Increasing a UTM virtual disk's size does not automatically expand the guest's APFS container. If a Recovery partition lies between the container's partition and the added free space, it blocks direct expansion.
This Codex/Astra-crafted script uses Python 3.9+ standard library and macOS diskutil, cp, and lsof; no pip packages, Go, Homebrew, or QEMU are required. It targets macOS 26+ with diskutil image support and an APFS host filesystem for copy-on-write backups:
Usage
Shut down the VM and do not start it while the script runs. Pass either the .utm bundle or its writable disk image. Inspection is the default:
python3 resize_utm.py '/path/macOS.utm' --size-gib 200
Apply the change, creating a new mandatory image backup first:
python3 resize_utm.py '/path/macOS.utm' --size-gib 200 --apply --backup '/path/macOS-before.img'
The size is GiB, so 200 means 214,748,364,800 bytes (Disk Utility displays about 214.7 GB). An initial 120 GiB image therefore gains 80 GiB. The main APFS container is smaller than the disk because ISC and outer Recovery retain their space.
What it does
- Resolves the single writable image from an Apple-backend UTM bundle, or accepts an image directly. Rejects physical devices, images currently open by another process, shrinking, and unexpected partition layouts.
- Attaches read-only for inspection. Validates both GPT headers and partition arrays, their CRCs, partition bounds/order, and Recovery's APFS superblock geometry.
- With
--apply, hashes the entire Recovery partition and creates a native APFS clone backup of the image.
- Uses
diskutil image resize --image-only to grow the image. On a tested macOS 27 build, this already relocates Recovery for ASIF images.
- If needed, relocates Recovery itself through the attached raw device. It derives usable space from actual device capacity and GPT array dimensions, preserves APFS block alignment, copies and SHA-256-verifies Recovery before updating GPT, and writes both GPT copies with new CRCs. Partition IDs, names, attributes, and unrelated entries are preserved.
- Checks Recovery against its original full SHA-256, detaches/reattaches to refresh the kernel's partition map, and expands the main APFS container using
diskutil apfs resizeContainer ... 0.
- Verifies the partition map and checks the final GPT layout fills the space up to Recovery. APFS's resize operation also performs a filesystem consistency check.
Only exactly three outer partitions, in physical order ISC / APFS / Apple_APFS_Recovery, are supported. Matching uses the Recovery type GUID, not a guessed name. Raw and ASIF image interpretation is delegated to macOS. The relocation core supports 512- and 4096-byte logical sectors. Growth smaller than or equal to Recovery's size is deliberately refused to avoid an overlapping copy. Already-grown images can be completed by requesting their existing capacity.
Failure and rollback
This is not a crash-atomic disk transaction. A crash during GPT commit can leave its copies inconsistent; validation then fails closed. The script does not infer missing Recovery data from an NXSB signature, and does not attempt to repair a partially executed third-party resizer. The retained image backup is the rollback source. A copy interruption before GPT commit leaves the original Recovery and GPT intact, although unused destination space may have changed.
If anything fails, leave the VM stopped. Eject any attachment listed in the log before restoring. Replace the modified image with a copy of the retained backup at the exact original image path. Do not restore only the old GPT after APFS growth.
An encrypted/locked APFS guest may require unlocking its Data volume before APFS expansion; the script reports the native error rather than asking for or recording passwords. Do not launch UTM or attach the image in another app during the run; the initial open-file check is not an exclusive lock against other applications.
References
The workflow is based on the UTM discussion and yunyang088's Go resizer. The discussion documents the stale-GPT-end bug, fixed here by computing geometry from actual device capacity. This implementation takes a different interruption strategy: verified nonoverlapping copy before GPT publication, with a mandatory whole-image backup for rollback.
Postscript
Briefly removed this post after an update to macOS 26.7 within the resized VM left it temporarily unbootable. However, further testing with an unresized copy, along with the reports below, indicates that the boot issue is unrelated to the resizing routine: Software Update to install Tahoe 26.7 fails on a blank screen | Updating a 26.6.2 virtual machine to 26.7 just black screens on restart
❧ 2026-09-18
- Settings → Users & Sharing → Aliases → + New alias → Show advanced preferences
- Enter alias address
- Under Deliver to, click x beside your address to remove it then enter the external address in its place → Add
- Add an optional Description → Save
❧ 2026-09-17
As for iOS 27, alas, not a single item on my longstanding, humble wishlist has been fulfilled. If Apple takes privacy and security as seriously as its marketing suggests, it would do well to learn from GrapheneOS.
Related
- Disable the blue "Writing Assistance" popup: System Settings → Screen Time → Restrictions → turn on "Content & Privacy" → (optional: Siri → Siri: Don't Allow Siri) → disable "Writing Assistance" (and other unwanted Capabilities as desired)
Disable AI-generated reply suggestions in Messages: System Settings → Keyboard → Text Input → Input Sources → Edit… → All Input Sources → disable "Show suggested replies"
If Safari's active tab is too bright in dark mode, turn off "Reduce Transparency" in System Settings → Accessibility → Display.
❧ 2026-09-15
USB storage bridges may expose ATA commands via SCSI/ATA Translation (SAT), but ATA Security behavior varies by bridge and firmware. Without an explicit vendor guarantee, reported capabilities alone do not confirm that a command can pass through the bridge and execute successfully on the drive.
To avoid orphaning data on the platters in the event of lockout, a 1 TB WD My Passport USB HDD was zeroed with dd then queried for ATA Security support:
hdparm -I /dev/sdX
/dev/sdX:
ATA device, with non-removable media
Model Number: WDC WD10JMVW-11AJGS1
...
Security:
Master password revision code = 48059
supported
not enabled
not locked
not frozen
not expired: security count
supported: enhanced erase
332min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
...
Setting a password appeared to work:
hdparm --user-master u --security-set-pass p /dev/sdX
security_password: "p"
/dev/sdX:
Issuing SECURITY_SET_PASS command, password="p", user=user, mode=high
but issuing SECURITY ERASE failed:
hdparm --user-master u --security-erase p /dev/sdX
security_password: "p"
/dev/sdX:
Issuing SECURITY_ERASE command, password="p", user=user
SG_IO: bad/missing sense data, sb[]: 70 00 05 00 00 00 00 0a 00 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
SG_IO: bad/missing sense data, sb[]: 70 00 05 00 00 00 00 0a 00 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Decoded SCSI sense data:
sg_decode_sense 70 00 05 00 00 00 00 0a 00 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Fixed format, current; Sense key: Illegal Request
Additional sense: Invalid field in cdb
In other words, something in the storage path reported that the SCSI command descriptor block (cdb) used to carry the ATA request contained an invalid or unsupported field.
hdparm -I /dev/sdX initially continued to return the correct device information. However, after disconnecting and reconnecting the drive, it reported only:
/dev/sdX:
ATA device, with non-removable media
Standards:
Likely used: 1
Configuration:
Logical max current
cylinders 0 0
heads 0 0
sectors/track 0 0
--
Logical/Physical Sector size: 512 bytes
device size with M = 1024*1024: 0 MBytes
device size with M = 1000*1000: 0 MBytes
cache/buffer size = unknown
Capabilities:
IORDY not likely
Cannot perform double-word IO
R/W multiple sector transfer: not supported
DMA: not supported
PIO: pio0
Attempting to unlock also failed:
hdparm --user-master u --security-unlock p /dev/sdX
security_password: "p"
/dev/sdX:
Issuing SECURITY_UNLOCK command, password="p", user=user
SG_IO: bad/missing sense data, sb[]: 70 00 05 00 00 00 00 0a 00 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
WD Drive Utilities restored access to the drive:
Launch WD Drive Utilities; the Unlock Drive prompt will appear:

The password set with hdparm will not be accepted; enter any password five times to reveal an erase option (the Drive Erase icon remains unavailable while the drive is locked):

Set volume name and format, acknowledge the warning, and click Erase Drive. The utility erases and reinitializes the drive, restoring access:


Updates
Restore access to the drive without leaving Linux by using the erase function in 0-duke's WD My Passport Drive Hardware Encryption Utility for Linux:
wdpassport-utils.py --device /dev/sdX
Device: /dev/sdX
Security status: Locked
Encryption type: Full Disk Encryption
Attempting to unlock the drive using the password set with hdparm fails, just as it does in WD's own utility:
wdpassport-utils.py --device /dev/sdX --unlock
[wdpassport] password for /dev/sdX:
[!] Key hash parameters are not valid.
Erasing the drive, however, restores access:
wdpassport-utils.py --device /dev/sdX --erase
[+] All data on /dev/sdX will be lost. Are you sure you want to continue? [y/N]
y
[*] Device erased. You need to create a new partition on the device (Hint: fdisk and mkfs)
hdparm -I /dev/sdX
/dev/sdX:
ATA device, with non-removable media
Model Number: WDC WD10JMVW-11AJGS1
...
device size with M = 1000*1000: 1000171 MBytes (1000 GB)
cache/buffer size = 8192 KBytes
Nominal Media Rotation Rate: 5400
...
Inspired by tigerblue77's AI-generated, zero-dependency unlock and eject scripts (themselves inspired by Kenny MacDermid's wdpassport-utils), I asked Astra to build a similar script for erasing WD My Passport drives. It produced erase_wd_passport.py in one shot, using only the Python 3 standard library: no pip packages, sg3-utils, hdparm, pyudev, or compiled extensions required. Tested on the 1TB drive mentioned above; your mileage may vary. All existing data will be lost, and the drive itself may be rendered unusable. Proceed at your own risk (and joy).
Usage
Without --erase, the script only queries WD encryption status:
python3 erase_wd_passport.py /dev/sdX
Device: /dev/sdX
USB product: My Passport 07A8
USB serial: XXXXXXXXXXXXXXXXXXXXXXXX
Security: Locked (0x01)
Cipher: 0x30; key length: 32 bytes
To erase, unmount any mounted filesystems on the disk, then run:
python3 erase_wd_passport.py /dev/sdX --erase
Device: /dev/sdX
USB product: My Passport 07A8
USB serial: XXXXXXXXXXXXXXXXXXXXXXXX
Security: Locked (0x01)
Cipher: 0x30; key length: 32 bytes
ALL data on this disk will become inaccessible. Keep it connected.
Type 'ERASE /dev/sdX' to continue: ERASE /dev/sdX
Sending WD key reset...
WD key-reset command completed successfully.
After reset: No lock (0x00)
Reported capacity: 1,000,170,586,112 bytes
First sector is readable.
Create a new partition table and filesystem before use.
What it does
The script implements the WD encryption-key reset from 0-duke/wdpassport-utils using standard-library ctypes and fcntl.ioctl. Before resetting, it verifies the whole-disk path and WD My Passport USB identity, checks for mounts, swap, and block-device holders, opens the device exclusively, and requires explicit confirmation. It then queries the drive's encryption status and current reset token and sends WD's vendor-specific key-reset command (C1 E3), intended to replace the internal data-encryption key and make existing data inaccessible. The request includes random bytes, following upstream's implementation, but whether the firmware uses them is unverified. Afterward, the script checks security status, capacity, and first-sector readability, then requests a partition rescan. These checks confirm basic access, not secure sanitization: the operation does not overwrite user-data sectors, create a filesystem, or perform ATA SECURITY ERASE. Firmware support varies, and this is not a general fix for "SG_IO: bad/missing sense data" errors.
Related
❧ 2026-09-12
8 months ago, u/Routine_Eye3806 announced Easy Disk Checker for Windows on r/datarecoverysoftware:
[Dev] I created Easy Disk Checker - a free Windows utility to check HDD\SSD health and detect fake USB drives with some data recovery options (No Ads)
and it has been receiving regular updates since:
Current highlights include:
- Drive diagnostics: SMART attributes, Seagate FARM logs, HPA and ATA password detection, and identification of the underlying drive behind USB bridges.
- Flash identification and testing: USB controller details, NAND IDs via controller-specific commands, fake-capacity detection, and surface scans for bad sectors.
- Imaging and cloning: Raw sector images, dynamic VHD/VHDX images, partition images, disk-to-disk cloning, and file extraction from images.
- Filesystem and partition recovery: MBR/GPT recovery and file browsing/copying across FAT, exFAT, NTFS, Ext2/3/4, HFS+, APFS, and BTRFS.
- RAID and DVR recovery: Automatic reconstruction of striped, mirrored, RAID-5, and RAID-6 arrays from disks or images, plus searches for DVR recordings.
- Low-level tools: Sector hex viewing/editing and, in version 6.0, microcode patching intended to restore file access on supported failed SSDs, starting with Phison-based models.
- Beyond storage: Reading and repairing errors in DDR4 and DDR5 memory SPD dumps.
In the last update, I asked u/Routine_Eye3806 if he had shared Easy Disk Checker on HDDGURU, to which he replied that he hadn't but would appreciate me sharing it there. My submission has not received moderator approval after 2 days, so I am posting it here instead.
Additional links:
❧ 2026-09-11
Download Windows 11 IoT Enterprise LTSC evaluation (ARM64 | x64) and utm-guest-tools-latest.iso.
Create the VM with "Install drivers and SPICE tools" unchecked (otherwise, clicking "I don't have a product key" during setup will fail with "Setup has failed to validate the product key").
Disconnect from the network, then install Windows.
Install UTM Guest Tools: Virtual Machine → Drives → CD/DVD…iso → Change → select utm-guest-tools-latest.iso → run D:\utm-guest-tools-0.1.271.exe. Eject when done.
Customize Windows as desired (taskbar, desktop background, dark mode, File Explorer, Edge, etc.), shut down, and save as your reference image.
Reconnect to the network, clone the reference VM, and boot the clone.
Windows should activate automatically and the desktop watermark change from "Windows License is expired" to "Windows License valid for 90 days". If not, run cscript //nologo %windir%\system32\slmgr.vbs /ato and reboot. If activation fails due to no network connectivity, try setting a public DNS server or switching from Shared Network to Bridged (Advanced).
❧ 2026-09-07
After years of happy Rectangle and Spectacle use, had missed that native window tiling was added in macOS Sequoia: Settings → Keyboard → Keyboard Shortcuts… → Windows.
Default keyboard shortcuts:
| Shortcut |
Action |
| Fn+Ctrl+← | Tile left half |
| Fn+Ctrl+→ | Tile right half |
| Fn+Ctrl+↑ | Tile top half |
| Fn+Ctrl+↓ | Tile bottom half |
| Fn+Ctrl+F | Fill (classic full screen) |
| Cmd+Ctrl+F | Full screen |
❧ 2026-09-07
These out-of-box experiences (OOBE) offer no obvious shutdown option, but each can still be powered off gracefully:
Windows: Shift+F10 → shutdown /s /t 0
macOS: Cmd+Q → Shut Down
Linux Mint (first boot after an OEM install): Ctrl+Alt+F2 → once the text console appears, quickly press and release the power button to shut down safely. GRUB may appear on next boot; one complete startup clears recordfail and re-hides the menu.
❧ 2026-09-05
Recover files from a failing drive without cloning posted to the docs section.
❧ 2026-08-20
"In one particular the civilized man still is brother to the savage: His thoughts seldom rise above the dust of which he is made."
—Robert Quillen, "When Guests Saw Only the Parlor, There Was Trash in the Closets", The State, October 22, 1929, p. 4.
❧ 2026-08-19