App2DMG

Page content

App2DMG

Recently, I found myself having to archive a bunch of Wineskin games that I created. I considered several approaches, from rsync to tar to archiving using zip/7zip.

I ultimately decided to create DMG files to archive these games. Wanting to also include an alias to the /Applications folder, I created a script to automate this.

Requirements

The following binaries are used within the script:

  • hdiutil
  • defaults
  • rsync
  • ln
hdiutil command

macOS includes the hdiutil utility, which allows managing/creating disk images from the command line.

A quick and simple way of creating a disk image from an Application Bundle (or a folder) would be:

 hdiutil create -format UDZO -srcfolder <path to source> <path to destination dmg>

One immediate issue with this approach is that if the app or folder is in a protected system folder, like /Applications, you will have to authenticate with admin rights.

Another issue is that if I wanted to include an alias to the /Applications folder in the DMG, I would not be able to do so.

defaults command

The defaults command allows you to view/extract information about an Application Bundle, like the version of the bundle itself.

rsync command

The rsync command is what I’ll be using to copy the Application Bundle to the destination disk image.

ln command

I’ll be using ln with the -s option to create a symlink/shortcut to the /Applications folder in the destination disk image.

Script

Here is the fully commented script:

#!/bin/bash

# ******************************************
# app2dmg.sh

# Bash script to create a DMG from
# an Application Bundle on macOS

# Written by Armando I. Rivera, BinaryMagic
# ******************************************

# Check that a single argument has been passed.
# If it hasn't, display usage and exit.
if [[ ! $# == 1 ]];then
    echo "Usage:  $(basename ${0}) <path to app bundle>"
    exit 1
fi

# Check that the passed argument is an app bundle or a folder.
# If it is, continue.  If it isn't, display warning message
if [[ -d "${1}" ]]; then

    # Remove full path from passed argument, leaving just the
    # name of the app bundle in the variable
    APP_BUNDLE=$(basename "${1}")

    # Remove the extension and store in variable
    APP_NAME="${APP_BUNDLE%.*}"

    # Retrieve the version of the app bundle, and store in variable
    APP_VERSION=$(defaults read "${1}/Contents/Info.plist" CFBundleShortVersionString)

    # Create a r/w SPARSE image with a maximum of 60GB of data,
    # and mount it
    echo "Creating '${APP_NAME}-${APP_VERSION}.dmg'..."
    hdiutil create -size 60G -type SPARSE -volname "${APP_NAME}-${APP_VERSION}" -fs JHFS+ -quiet "${APP_NAME}.sparseimage"
    hdiutil attach -quiet "${APP_NAME}.sparseimage"

    # Copy the app bundle to the SPARSE image
    echo "Copying '${APP_BUNDLE}' to Disk Image..."
    rsync -aEh "${1}" "/Volumes/${APP_NAME}-${APP_VERSION}"

    # Create a shortcut to the local Applications folder
    # on the SPARSE image
    ln -s /Applications "/Volumes/${APP_NAME}-${APP_VERSION}"

    # Unmount the SPARSE Image
    hdiutil eject -quiet "/Volumes/${APP_NAME}-${APP_VERSION}"

    # Convert the SPARSE image to the final compressed DMG file
    hdiutil convert -format UDZO -quiet "${APP_NAME}.sparseimage" -o "${APP_NAME}-${APP_VERSION}.dmg"

    # Delete the SPARSE image, which is no longer needed
    rm "${APP_NAME}.sparseimage"

    echo "'${APP_NAME}-${APP_VERSION}.dmg' has been created."
else
    echo "Path ${1} not found."
    exit 1
fi

Script Output

Using HandBrake.app as an example, here is the script output:

app2dmg.sh /Applications/HandBrake.app
Creating 'HandBrake-1.2.0.dmg'...
Copying 'HandBrake.app' to Disk Image...
'HandBrake-1.2.0.dmg' has been created.

Screen Shot