Disk image of OS + Recovery partition?

Starting from a working setup

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:

  1. 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
    
  2. 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
    
  3. 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
    
  4. 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
    
  5. 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).


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.

  1. 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
    
  2. 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.

  1. 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   
    
  2. 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
    
  3. 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
    
  4. 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
    
  5. 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
    
  6. 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
    
  7. 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:

  1. Mount the image using

    # hdiutil attach /tmp/backup_image.dmg -nomount
    
  2. 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