How do I create a nice-looking DMG for Mac OS X using command-line tools?
Asked Answered
L

15

233

I need to create a nice installer for a Mac application. I want it to be a disk image (DMG), with a predefined size, layout and background image.

I need to do this programmatically in a script, to be integrated in an existing build system (more of a pack system really, since it only create installers. The builds are done separately).

I already have the DMG creation done using "hdiutil", what I haven't found out yet is how to make an icon layout and specify a background bitmap.

Lakeishalakeland answered 18/9, 2008 at 20:58 Comment(3)
Isn't this something for Ask Different?Reid
github.com/LinusU/node-appdmgGaniats
Since this has so many upvotes, it should be noted that every JDK now comes with jpackage which can create .dmg files. No need for third party tools.Zurn
L
210

After lots of research, I've come up with this answer, and I'm hereby putting it here as an answer for my own question, for reference:

  1. Make sure that "Enable access for assistive devices" is checked in System Preferences>>Universal Access. It is required for the AppleScript to work. You may have to reboot after this change (it doesn't work otherwise on Mac OS X Server 10.4).

  2. Create a R/W DMG. It must be larger than the result will be. In this example, the bash variable "size" contains the size in Kb and the contents of the folder in the "source" bash variable will be copied into the DMG:

    hdiutil create -srcfolder "${source}" -volname "${title}" -fs HFS+ \
          -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${size}k pack.temp.dmg
    
  3. Mount the disk image, and store the device name (you might want to use sleep for a few seconds after this operation):

    device=$(hdiutil attach -readwrite -noverify -noautoopen "pack.temp.dmg" | \
             egrep '^/dev/' | sed 1q | awk '{print $1}')
    
  4. Store the background picture (in PNG format) in a folder called ".background" in the DMG, and store its name in the "backgroundPictureName" variable.

  5. Use AppleScript to set the visual styles (name of .app must be in bash variable "applicationName", use variables for the other properties as needed):

    echo '
       tell application "Finder"
         tell disk "'${title}'"
               open
               set current view of container window to icon view
               set toolbar visible of container window to false
               set statusbar visible of container window to false
               set the bounds of container window to {400, 100, 885, 430}
               set theViewOptions to the icon view options of container window
               set arrangement of theViewOptions to not arranged
               set icon size of theViewOptions to 72
               set background picture of theViewOptions to file ".background:'${backgroundPictureName}'"
               make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
               set position of item "'${applicationName}'" of container window to {100, 100}
               set position of item "Applications" of container window to {375, 100}
               update without registering applications
               delay 5
               close
         end tell
       end tell
    ' | osascript
    
  6. Finialize the DMG by setting permissions properly, compressing and releasing it:

    chmod -Rf go-w /Volumes/"${title}"
    sync
    sync
    hdiutil detach ${device}
    hdiutil convert "/pack.temp.dmg" -format UDZO -imagekey zlib-level=9 -o "${finalDMGName}"
    rm -f /pack.temp.dmg 
    

On Snow Leopard, the above applescript will not set the icon position correctly - it seems to be a Snow Leopard bug. One workaround is to simply call close/open after setting the icons, i.e.:

..
set position of item "'${applicationName}'" of container window to {100, 100}
set position of item "Applications" of container window to {375, 100}
close
open
Lakeishalakeland answered 18/9, 2008 at 20:58 Comment(10)
Excellent. Two questions about that: 1. You recommend sleep after mounding the image. How long? Isn’t it possible to deterministically wait for the completion of the process? Same for delay 5 in your AppleScript. I’m always wary of such arbitrary wait times, having had some very bad experience with them. 2. In your step 6 you call sync twice – why?Clareta
I haven't found any way to deterministically wait for the completion of the 'update without registering applications' command. I am not sure sleep is needed after "hdiutil attach", you'll have to check the documentation (man hdiutil). Sync should only be needed to be called once, I do it twice out of old habit since the good old SunOS days.Outspoken
It allows applescript to simulate mouse and keyboard input and a bunch of other things.Outspoken
If the script hangs until it times out at the "update without registering applications" line, then step 1 hasn't been done (or has become undone). I just found this out the hard way.Hoshi
I think I found a slight glitch - there is an eject in the AppleScript, which is done before you try to run chmod. chmod will fail because the disk is now ejected.Conditioning
chmod is totally pointless because it would require sudo and doesn't accomplish anything. The resulting DMG is completely read-only regardless.Mouthwash
Removing the write permissions on the source folder was required in one of the earlier releases of hdiutil. It is assumed in this article that the commands are executed as root, which may or may not be needed for all other parts to work. The current version of hdiutil doesn't care about the write privileges on the source folder.Outspoken
I'm not able to create the shortcut for the "/Applications" folder using the above steps. I'm getting a "Blank file" in the place of the "/Application" folder shortcut.Grillage
Thank you! Almost works with Mojave, just icons are not updated and setting background does not work. Also, in your step 6, the leading slashes do not seem to make sense to me (and at least render the script unusable)Stubbs
This was very helpful, in that my script was almost working, but not generating the .DS_Store file. What seems to have been the critical missing bit was the "update without registering applications" part. Which begs the question, why, for the love of all that is holy, are we still stuck with this AppleScript nonsense in 2023? Apple seems capable of creating APIs - why isn't there an API or command line tools for generating .DS_Store files?Jacques
N
65

There's a little Bash script called create-dmg that builds fancy DMGs with custom backgrounds, custom icon positioning and volume name.

I've built it many years ago for the company that I ran at the time; it survives on other people's contribution since then, and reportedly works well.

There's also node-appdmg which looks like a more modern and active effort based on Node.js; check it out as well.

Nerty answered 18/9, 2008 at 20:58 Comment(10)
The link to your bash script is broken. Can you put it in a gist on github.com? ThanksJorie
create-dmg does NOT position icon well. If you need this feature, don't use it.Planchette
@Planchette Can you please report it as an issue, if you know specifics? Maybe someone will fix it.Nerty
@AndreyTarantsov There's already an issue github.com/andreyvit/create-dmg/issues/18.Planchette
@AndreyTarantsov Eh, it's your project, I thought you're aware of all those issues...Planchette
@Planchette It's been more than 5 years since I've last touched it. I don't really consider DMGs the best way to distribute Mac software any more. So it survives on contributions of others — hopefully someone will figure out the icons issue too.Nerty
@AndreyTarantsov Which way do you think is the best for Mac apps distribution?Dorothi
@Dorothi Well as a user, I prefer zip files by far. Bonus points for auto-copying to /Applications if the app detects it's running from ~/Downloads. See also this old article by John Gruber.Nerty
@Dorothi Also see this article with a screenshot of the UI involved.Nerty
Note: this will not work on headless builds, for instance in a Jenkins continuous integration server. I suggest the use of dmgbuild that do not require "Finder" to be available.Izanagi
S
42

Don't go there. As a long term Mac developer, I can assure you, no solution is really working well. I tried so many solutions, but they are all not too good. I think the problem is that Apple does not really document the meta data format for the necessary data.

Here's how I'm doing it for a long time, very successfully:

  1. Create a new DMG, writeable(!), big enough to hold the expected binary and extra files like readme (sparse might work).

  2. Mount the DMG and give it a layout manually in Finder or with whatever tools suits you for doing that. The background image is usually an image we put into a hidden folder (".something") on the DMG. Put a copy of your app there (any version, even outdated one will do). Copy other files (aliases, readme, etc.) you want there, again, outdated versions will do just fine. Make sure icons have the right sizes and positions (IOW, layout the DMG the way you want it to be).

  3. Unmount the DMG again, all settings should be stored by now.

  4. Write a create DMG script, that works as follows:

  • It copies the DMG, so the original one is never touched again.
  • It mounts the copy.
  • It replaces all files with the most up to date ones (e.g. latest app after build). You can simply use mv or ditto for that on command line. Note, when you replace a file like that, the icon will stay the same, the position will stay the same, everything but the file (or directory) content stays the same (at least with ditto, which we usually use for that task). You can of course also replace the background image with another one (just make sure it has the same dimensions).
  • After replacing the files, make the script unmount the DMG copy again.
  • Finally call hdiutil to convert the writable, to a compressed (and such not writable) DMG.

This method may not sound optimal, but trust me, it works really well in practice. You can put the original DMG (DMG template) even under version control (e.g. SVN), so if you ever accidentally change/destroy it, you can just go back to a revision where it was still okay. You can add the DMG template to your Xcode project, together with all other files that belong onto the DMG (readme, URL file, background image), all under version control and then create a target (e.g. external target named "Create DMG") and there run the DMG script of above and add your old main target as dependent target. You can access files in the Xcode tree using ${SRCROOT} in the script (is always the source root of your product) and you can access build products by using ${BUILT_PRODUCTS_DIR} (is always the directory where Xcode creates the build results).

Result: Actually Xcode can produce the DMG at the end of the build. A DMG that is ready to release. Not only you can create a release DMG pretty easy that way, you can actually do so in an automated process (on a headless server if you like), using xcodebuild from command line (automated nightly builds for example).

Seppuku answered 18/9, 2008 at 21:11 Comment(8)
I've already discarded the idea of doing it this way, for several reasons. Here are two of them: the contents of the installers will vary with product, and we want to rely only on software installed on the pack machines and scripts - a single, minimal, manual routine for adding new products.Outspoken
This is the same scenario as we have. We have more than a dozen of products; each has an entirely different DMG. Creating one template DMG per product is one time only task and takes you a couple of minutes. And what do you mean by "installer"? PKG/MPKG install packages?Seppuku
It's not the same scenario. We add products often, with short notice. The minimal manual routine means running a script giving the product a name and a few other attributes. There are reasons beyond this as well that made us make the decision not to use that kind of solution.Outspoken
We have separated the pack process from the build process, because it is done by different people at different times. The pack process creates installers for Windows, Windows CE, Symbian, AIX, Linux and Solaris as well.Outspoken
You are probably refering to hdiutil, not hdutil.Shumate
For me - the scripted version above failed to work (it would consistently refuse to create the Applications link). Using this method worked well and reliably despite requiring such a manual process to set up.Sunn
If you want the disk image root window to open when the image is mounted, add the following to step 2: bless --openfolder "/Volumes/My Great Software".Carnify
Seems like an ugly way to do it, but after striking out trying the above approaches, I'm giving this a whirl. But after I replace the files and detach, my image is unchanged and does not contain the new files. What might I be doing wrong? hdiutil attach "$COPY_OF_IMAGE" -readwrite -mountpoint "$STAGING_DIR" find "$DEST_KM_APP" -mindepth 1 -maxdepth 1 -print0 | xargs -0 rm -rf cp -fR "$SOURCE_KM_APP/" "$DEST_KM_APP" hdiutil detach $STAGING_DIR $VERBOSITYKeelby
C
25

Bringing this question up to date by providing this answer.

appdmg is a simple, easy-to-use, open-source command line program that creates dmg-files from a simple json specification. Take a look at the readme at the official website:

https://github.com/LinusU/node-appdmg

Quick example:

  1. Install appdmg

    npm install -g appdmg
    
  2. Write a json file (spec.json)

    {
      "title": "Test Title",
      "background": "background.png",
      "icon-size": 80,
      "contents": [
        { "x": 192, "y": 344, "type": "file", "path": "TestApp.app" },
        { "x": 448, "y": 344, "type": "link", "path": "/Applications" }
      ]
    }
    
  3. Run program

    appdmg spec.json test.dmg
    

(disclaimer. I'm the creator of appdmg)

Cookery answered 18/9, 2008 at 20:58 Comment(5)
simple and effective. The only downside is it requires npm to be installedLanthanum
@Creator: can you please make it advance like this one offer GUI without having to drag and drop? s.sudre.free.fr/Software/Packages/about.htmlEpileptoid
If the .app file isn't in the same folder as the json file, this doesn't work, it gives "file not found" error when you specify a relative path in "path"Chromatogram
@Joey, could you open an issue on the Github repository if it doesn't work for you?Hemia
@Joey: It is not required to be in the same folder, I have background, app all in different path, just pass relative paths as ../../../ABCCommutation
O
23

For those of you that are interested in this topic, I should mention how I create the DMG:

hdiutil create XXX.dmg -volname "YYY" -fs HFS+ -srcfolder "ZZZ"

where

XXX == disk image file name (duh!)
YYY == window title displayed when DMG is opened
ZZZ == Path to a folder containing the files that will be copied into the DMG
Outspoken answered 18/9, 2008 at 21:13 Comment(2)
That's fine, but it doesn't address the actual need (background image, positioning of items in the folder, etc.)Keelby
created DMG but again i have to run my Script(.sh) using CMD i need its automatically run the sh after creating DMGBuxton
M
14

My app, DropDMG, is an easy way to create disk images with background pictures, icon layouts, custom volume icons, and software license agreements. It can be controlled from a build system via the "dropdmg" command-line tool or AppleScript. If desired, the picture and license RTF files can be stored under your version control system.

Microampere answered 18/9, 2008 at 20:58 Comment(6)
My team has this working beautifully and automatically on a Jenkins CI build server, complete with background, drag to Applications alias. Run, don't walk, to DropDMG for making disk images.Saurischian
Good App, I'm going to purchase it after my trial expires.Magalimagallanes
Looks nice, but it does not seem to have option to resize the window.Pina
@Pina DropDMG automatically sizes the window to the background picture that you set (or inset into that picture if desired).Microampere
Great, thanks! Is it possible to resize without background image?Pina
@Pina If you are not using a background picture, you can set the dimensions using the “BlankBackgroundSize” esoteric preference.Microampere
H
9

For creating a nice looking DMG, you can now just use some well written open sources:

Haddock answered 18/9, 2008 at 20:58 Comment(2)
Might be they moved it. You can use create-dmg and node-appdmg. I'm using create-dmg. Its good.Haddock
@PamelaCook-LightBeCorp In case you are still interested. The link to dmgbuild has been fixed.Stooge
T
6

I found this great mac app to automate the process - http://www.araelium.com/dmgcanvas/ you must have a look if you are creating dmg installer for your mac app

Thorianite answered 18/9, 2008 at 20:58 Comment(1)
Note that this is not a free program, so it's not suitable for many environments.Hoshi
G
5

I finally got this working in my own project (which happens to be in Xcode). Adding these 3 scripts to your build phase will automatically create a Disk Image for your product that is nice and neat. All you have to do is build your project and the DMG will be waiting in your products folder.

Script 1 (Create Temp Disk Image):

#!/bin/bash
#Create a R/W DMG

dir="$TEMP_FILES_DIR/disk"
dmg="$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.temp.dmg"

rm -rf "$dir"
mkdir "$dir"
cp -R "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app" "$dir"
ln -s "/Applications" "$dir/Applications"
mkdir "$dir/.background"
cp "$PROJECT_DIR/$PROJECT_NAME/some_image.png" "$dir/.background"
rm -f "$dmg"
hdiutil create "$dmg" -srcfolder "$dir" -volname "$PRODUCT_NAME" -format UDRW

#Mount the disk image, and store the device name
hdiutil attach "$dmg" -noverify -noautoopen -readwrite

Script 2 (Set Window Properties Script):

#!/usr/bin/osascript
#get the dimensions of the main window using a bash script

set {width, height, scale} to words of (do shell script "system_profiler SPDisplaysDataType | awk '/Main Display: Yes/{found=1} /Resolution/{width=$2; height=$4} /Retina/{scale=($2 == \"Yes\" ? 2 : 1)} /^ {8}[^ ]+/{if(found) {exit}; scale=1} END{printf \"%d %d %d\\n\", width, height, scale}'")
set x to ((width / 2) / scale)
set y to ((height / 2) / scale)

#get the product name using a bash script
set {product_name} to words of (do shell script "printf \"%s\", $PRODUCT_NAME")
set background to alias ("Volumes:"&product_name&":.background:some_image.png")

tell application "Finder"
    tell disk product_name
        open
        set current view of container window to icon view
        set toolbar visible of container window to false
        set statusbar visible of container window to false
        set the bounds of container window to {x, y, (x + 479), (y + 383)}
        set theViewOptions to the icon view options of container window
        set arrangement of theViewOptions to not arranged
        set icon size of theViewOptions to 128
        set background picture of theViewOptions to background
        set position of item (product_name & ".app") of container window to {100, 225}
        set position of item "Applications" of container window to {375, 225}
        update without registering applications
        close
    end tell
end tell

The above measurement for the window work for my project specifically due to the size of my background pic and icon resolution; you may need to modify these values for your own project.

Script 3 (Make Final Disk Image Script):

#!/bin/bash
dir="$TEMP_FILES_DIR/disk"
cp "$PROJECT_DIR/$PROJECT_NAME/some_other_image.png" "$dir/"

#unmount the temp image file, then convert it to final image file
sync
sync
hdiutil detach /Volumes/$PRODUCT_NAME
rm -f "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.dmg"
hdiutil convert "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.temp.dmg" -format UDZO -imagekey zlib-level=9 -o "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.dmg"
rm -f "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.temp.dmg"

#Change the icon of the image file
sips -i "$dir/some_other_image.png"
DeRez -only icns "$dir/some_other_image.png" > "$dir/tmpicns.rsrc"
Rez -append "$dir/tmpicns.rsrc" -o "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.dmg"
SetFile -a C "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.dmg"

rm -rf "$dir"

Make sure the image files you are using are in the $PROJECT_DIR/$PROJECT_NAME/ directory!

Go answered 18/9, 2008 at 20:58 Comment(2)
It is creating blank disk image on my project . Any suggestion.Adenocarcinoma
Shell script part works only, but how can I add Apple script with Shell Script in RunScript under Build Phase it is showing error ,every apple script statement is symbol not found.Adenocarcinoma
M
5

If you want to set custom volume icon then use below command

/*Add a drive icon*/
cp "/Volumes/customIcon.icns" "/Volumes/dmgName/.VolumeIcon.icns"  


/*SetFile -c icnC will change the creator of the file to icnC*/
SetFile -c icnC /<your path>/.VolumeIcon.icns

Now create read/write dmg

/*to set custom icon attribute*/
SetFile -a C /Volumes/dmgName
Monitor answered 18/9, 2008 at 20:58 Comment(3)
Can you explain what does the "your path" mean here? Can I use any icons file on the disk, and SetFile would copy it, or would I need to use a file that is inside .dmg? I only have one Mac, so it's hard to test if stuff will work on other machines.Lemuel
"your path" is DMG name. (/Volumes/dmgName)Monitor
You should copy icns file. cp "/Volumes/customIcon.icns" "/Volumes/dmgName/.VolumeIcon.icns"Monitor
P
2

I used dmgbuild.

  • Installation: pip3 install dmgbuild
  • Mount your volume
  • Create a settings file:
{
    "title": "NAME",
    "background": "YOUR_BACKGROUND.png",
    "format": "UDZO",
    "compression-level": 9,
    "window": { "position": { "x": 100, "y": 100 },
                "size": { "width": 640, "height": 300 } },
    "contents": [
        { "x": 140, "y": 165, "type": "file", "path": "/Volumes/YOUR_VOLUME_NAME/YOUR_APP.app" },
        { "x": 480, "y": 165, "type": "link", "path": "/Applications" }
    ]
}
  • The width value is the width of the background.

  • The height value should be the background height + 20 for the window bar.

  • In a terminal: dmgbuild -s settings.json "YOUR_VOLUME_NAME" output.dmg

Pippin answered 18/9, 2008 at 20:58 Comment(0)
R
2

I've just written a new (friendly) command line utility to do this. It doesn’t rely on Finder/AppleScript, or on any of the (deprecated) Alias Manager APIs, and it’s easy to configure and use.

Anyway, anyone who is interested can find it on PyPi; the documentation is available on Read The Docs.

Richierichlad answered 18/9, 2008 at 20:58 Comment(0)
U
2

I also in need of using command line approach to do the packaging and dmg creation "programmatically in a script". The best answer I found so far is from Adium project' Release building framework (See R1). There is a custom script(AdiumApplescriptRunner) to allow you avoid OSX WindowsServer GUI interaction. "osascript applescript.scpt" approach require you to login as builder and run the dmg creation from a command line vt100 session.

OSX package management system is not so advanced compared to other Unixen which can do this task easily and systematically.

R1: http://hg.adium.im/adium-1.4/file/00d944a3ef16/Release

Undine answered 18/9, 2008 at 20:58 Comment(0)
G
2

.DS_Store files stores windows settings in Mac. Windows settings include the icons layout, the window background, the size of the window, etc. The .DS_Store file is needed in creating the window for mounted images to preserve the arrangement of files and the windows background.

Once you have .DS_Store file created, you can just copy it to your created installer (DMG).

Generosity answered 18/9, 2008 at 20:58 Comment(1)
But first add a background image to the dmg by naming it something like .background.png and then using Cmd-J to add that (!) image to the background.Protrusile
T
1

These answers are way too complicated and times have changed. The following works on 10.9 just fine, permissions are correct and it looks nice.

Create a read-only DMG from a directory

#!/bin/sh
# create_dmg Frobulator Frobulator.dmg path/to/frobulator/dir [ 'Your Code Sign Identity' ]
set -e

VOLNAME="$1"
DMG="$2"
SRC_DIR="$3"
CODESIGN_IDENTITY="$4"

hdiutil create -srcfolder "$SRC_DIR" \
  -volname "$VOLNAME" \
  -fs HFS+ -fsargs "-c c=64,a=16,e=16" \
  -format UDZO -imagekey zlib-level=9 "$DMG"

if [ -n "$CODESIGN_IDENTITY" ]; then
  codesign -s "$CODESIGN_IDENTITY" -v "$DMG"
fi

Create read-only DMG with an icon (.icns type)

#!/bin/sh
# create_dmg_with_icon Frobulator Frobulator.dmg path/to/frobulator/dir path/to/someicon.icns [ 'Your Code Sign Identity' ]
set -e
VOLNAME="$1"
DMG="$2"
SRC_DIR="$3"
ICON_FILE="$4"
CODESIGN_IDENTITY="$5"

TMP_DMG="$(mktemp -u -t XXXXXXX)"
trap 'RESULT=$?; rm -f "$TMP_DMG"; exit $RESULT' INT QUIT TERM EXIT
hdiutil create -srcfolder "$SRC_DIR" -volname "$VOLNAME" -fs HFS+ \
               -fsargs "-c c=64,a=16,e=16" -format UDRW "$TMP_DMG"
TMP_DMG="${TMP_DMG}.dmg" # because OSX appends .dmg
DEVICE="$(hdiutil attach -readwrite -noautoopen "$TMP_DMG" | awk 'NR==1{print$1}')"
VOLUME="$(mount | grep "$DEVICE" | sed 's/^[^ ]* on //;s/ ([^)]*)$//')"
# start of DMG changes
cp "$ICON_FILE" "$VOLUME/.VolumeIcon.icns"
SetFile -c icnC "$VOLUME/.VolumeIcon.icns"
SetFile -a C "$VOLUME"
# end of DMG changes
hdiutil detach "$DEVICE"
hdiutil convert "$TMP_DMG" -format UDZO -imagekey zlib-level=9 -o "$DMG"
if [ -n "$CODESIGN_IDENTITY" ]; then
  codesign -s "$CODESIGN_IDENTITY" -v "$DMG"
fi

If anything else needs to happen, these easiest thing is to make a temporary copy of the SRC_DIR and apply changes to that before creating a DMG.

Tobias answered 18/9, 2008 at 20:58 Comment(1)
This answer is not adding anything to what's been written earlier, and it also does not answer the original question (it's not about just putting an icon in the dmg, or how to sign it; the question is about adding graphics and positioned icons programmatically to a dmg).Outspoken

© 2022 - 2024 — McMap. All rights reserved.