Starting from a working setup (HFS-era macOS)
A working setup is disk, or full-disk image with multiple partitions (FDI hereafter), where the OS and its recovery partition is known to be correct, most likely because it was created by the OS Installer. As mentioned in the comment, asr is enough to handle copying both partitions in this case.
Assuming the source OS and recovery partitions are disk3s11 and disk3s12, respectively, I recommend the following procedure:
Shrink the OS partition to its minimum size. This isn't necessary, but I suggest doing it when you can never be sure if the user's target partition will be as big as the one you're using.
$ diskutil resizeVolume disk3s11 limits ... (minimum size will be mentioned here) ... $ diskutil resizeVolume disk3s11 24GB
Create appropriately-sized temporary FDI as a sparsebundle. Remember, the Recovery takes extra 650 MB over the system partition, and EFI takes another 210 MB. So I'll add an extra GB to the size used in step 1 just to be safe. The catch here is, hdiudil support only binary prefixes (1024 for k, etc.). In this example, 25 GB = 23.29 GiB.
$ hdiutil create -layout GPTSPUD -type SPARSEBUNDLE -size 23.29g /tmp/temp_FDI created: /tmp/temp_FDI.sparsebundle
Attach the image without mounting it (it would fail, since the partition was not formatted). Take note of the /dev/ entry for the Apple_HFS partition.
$ hdiutil attach /tmp/temp_FDI.sparsebundle -nomount /dev/disk5 GUID_partition_scheme /dev/disk5s1 EFI /dev/disk5s2 Apple_HFS
Run asr with the image's empty partition as the target. You'll have to enter full path to /dev/... entries. Also, since the source is a physical disk, elevated privileges are necessary. You should see the tool finish with 2 rows of Restoring... progress.
$ sudo asr restore --source /dev/disk3s11 --target /dev/disk5s2 --erase --noverify Validating target...done Validating source...done Erase contents of /dev/disk5s2 ()? [ny]: y Validating sizes...done Restoring ....10....20....30....40....50....60....70....80....90....100 Restoring ....10....20....30....40....50....60....70....80....90....100
Finally, create a compressed read-only image from the sparsebundle. Use the sparsebundle's "whole disk" /dev/ entry (/dev/disk5).
$ hdiutil create -format ULFO -srcdevice /dev/disk5 -o /tmp/OS_X_10.11-combined_FDI2.dmg ... Elapsed Time: 2m 33.743s File size: 8000565446 bytes, Checksum: CRC32 $5D496986 Sectors processed: 48842670, 38032196 compressed Speed: 120.8Mbytes/sec Savings: 68.0% created: /tmp/OS_X_10.11-combined_FDI2.dmg
You may wish to use UDZO format if you need your image to be compatible with older versions of OS X and their respective Recovery environments (ULFO was only introduced in 10.11).
Starting from a working setup (APFS-era macOS)
In the realm of modern macOS, use of asr is effectively mandatory, as the APFS volumes, even though they appear as device nodes, cannot be successfully copied individually. I'd refer anyone to read asr's manual page (on the full system, not online, where it hasn't been updated in years). It covers all relevant advanced topics such as snapshots (when copying from a snapshot, the same snapshot is created on the target), read-only system-volume (the volume group is almost always treated as indivisible unit, and you can refer to it through either volume).
The trivial case is actually simpler than with HFS, as with APFS there's no need to shrink the source. The procedure is as follows:
- Create a blank, writable image, large enough to hold the data of the system/data volumes, Recovery, Preboot.
1.2. You can use diskutil apfs list's output to calculate the total size needed (sum of Capacity Consumed from each of the relevant volumes) if you wish to keep the created container to a minimum size, but again - this is unnecessary with asr/APFS.
1.3 Note that if using sparse/bundle type, you will most likely need local storage to hold this image, network storage might not work. The command below relies on defaults used in contemporary macOS versions - GPT partitioning, APFS container with a single volume inside it.
% hdiutil create -size 120g -type SPARSEBUNDLE ~/Desktop/OS-image
Attach the image, without mounting. Note that we are dealing with 2 nodes for "disk" - one for the image's block storage (a GPT disk), and another for the APFS synthesised disk under which APFS volumes will appear:
% hdiutil attach -nomount ~/Desktop/OS-image.sparsebundle /dev/disk4 GUID_partition_scheme /dev/disk4s1 EFI /dev/disk4s2 Apple_APFS /dev/disk5 EF57347C-0000-11AA-AA11-0030654 /dev/disk5s1 41504653-0000-11AA-AA11-0030654
Perform asr restore, specifying the desired system volume as source, and the blank APFS container, or its partition in the image as target:
% sudo asr restore --source /Volumes/Macintosh\ HD --target /dev/disk4s2 --erase Validating target...done Validating source...done Erase contents of /dev/disk5 ()? [ny]: y Replicating ....10....20....30....40....50....60....70....80....90....100 Replicating ....10....20....30....40....50....60....70....80....90....100 Restored target device is /dev/disk5s2. Restore completed successfully.
We can't have the intermediary image mounted for the last step, so unmount eject the whole thing:
% diskutil eject disk4
Finally, create a compressed read-only image from the sparsebundle. This should work successfully with network storage.
% hdiutil convert -format ULMO -o /Volumes/NAS-public/OS-image ~/Desktop/OS-image.sparsebundle
Working from the ground up
It is possible to assemble a "working setup" from single-partition images (images which don't have partition table, and therefore no hint for asr that a particular partition is a Recovery; SPI hereafter). The procedure is not for the faint of heart.
I'm going to assume that I have a compressed, read-only SPIs of both the system partition, and the recovery partition. Shrinking the partitions to minimum size is a whole another can of worms, where using a full-blown UDRW image seems unavoidable. Feel free to suggest a simpler solution.
With that in mind, do a pre-shrink of the system partition image. Readonly image can only be shrunk with a shadow.
$ hdiutil resize -sectors min OS_X_10.11-system.dmg -shadow /tmp/pre-shrink.shadow
Check the image size, then attach it
$ hdiutil imageinfo OS_X_10.11-system.dmg -shadow /tmp/pre-shrink.shadow ... Total Bytes: 27648868352 $ hdiutil attach OS_X_10.11-system.dmg -shadow /tmp/pre-shrink.shadow -nomount ... /dev/disk7
The size is about 27.65 GB.
Create a sparse image big enough to hold the system and recovery partitions in full size - AFAICT, SPIs cannot be resized and that must be done inside an FDI. With extra space needed for EFI and recovery, we'll need 28.6 GB = 26.64 GiB.
$ hdiutil create -layout GPTSPUD -type SPARSEBUNDLE -size 26.64g /tmp/temp_FDI $ hdiutil attach /tmp/temp_FDI.sparsebundle -nomount /dev/disk5 GUID_partition_scheme /dev/disk5s1 EFI /dev/disk5s2 Apple_HFS
Do an asr restore from the system SPI's /dev/ node into the sparsebundle's data partition.
$ asr restore --source /dev/disk7 --target /dev/disk5s2 --erase --noverify
Now, check the minimum size of the partition using diskutil. Then use resizeVolume to shrink the partition and create the recovery partition in a single step.
$ diskutil mount disk5s2 $ diskutil resizeVolume disk5s2 limits ... (minimum size will be mentioned here) ... $ diskutil resizeVolume disk5s2 24GB JHFS+ Recovery %recovery% Free\ Space dummy 1m
Restore the recovery SPI into the new partition on the FDI. It will probably get mounted, so unmount it. You can work with the image directly.
$ asr restoreexact --source /path/to/OS_X_10.11-recovery.dmg --target /dev/disk5s3 --erase --noverify ... $ diskutil umount disk5s3
Here's the tricky bit - dump the partition table using the gpt utility, taking note of the recovery partition's start and size values. Delete the record for the recovery partition, and re-add it with modified type GUID. You will know which record is for the recovery partition by the index ("disk5s3"), and by its size (shown here in 512 B sectors).
$ gpt show disk5 ... 47546784 1269536 3 GPT part - "Recovery" ... $ gpt remove -i 3 disk5 $ gpt add -b 47546784 -s 1269536 -t 426F6F74-0000-11AA-AA11-00306543ECAC disk5
Optional: I also recommend changing the partition name (set to "disk image" by default) to match the system partition's volume name.
$ gpt label -i 2 -l "OS X 10.11" disk5
Now, since hdiutil resize doesn't work with sparsebundles the way we'd need, you can either:
- Leave it at that. It's not like the free space consumes anything. Continue with step 5 in the Working setup scenario.
- Convert the temp_FDI.sparsebundle image to UDRW, and try to properly shrink that. I haven't tested that and the need to write out the entire size image for this step makes the whole exercise pointless.
- Treat the sparsebundle existing at this point as a disk with optimally-shrunk system, and perform the first scenario on it, skipping step 1. Ie. create another sparsebundle image, and run
asr, which will this time copy both partitions into the new image, and convert that to ULFO image.
Restoring
Steps for restoring from the created image:
Mount the image using
# hdiutil attach /tmp/backup_image.dmg -nomount
Start the restore, either by:
Use the "Disk Utility" app, restoring from the partition in the backup_image to the partition on your drive (the one in Sierra just works, restoring the Recovery partition too: you will see two Restoring/Verifying steps)
Use asr, similarly as described above:
$ asr restore --source /dev/disk1s2 --target /dev/disk0s4 --erase --noverify
diskutil'sresizeVolumecommand, which can add partitions in the free space after the partition being worked on. - The Recovery partition differs only in its GUID type in the GPT table. Using thegptcommand, removing the partition and re-adding it with proper GUID achieves the desired state.asr, at least the version in OS X 10.11, will restore both the system partition and the recovery immediately following. If one is starting with a disk which already has both OS and partition, thenasrshould be enough to do everything. So really, the real issue is that the GUI Disk Utility creates images of partitions and single-filesystem images which don't hold the accompanying recovery partition.