Mac Installers

Article:
Mac Installers

Updated: May 31, 2010

There are two types of installers for MacOSX:

  1. Managed Installers ("Packages")
  2. Manual Installers
Mac InstallersApple recommend a Managed Installer for Applications which require resources to be installed in different parts of the file system. For example: your delivery includes an Application, a Preference Panel, Fonts and Documentation. Apple provide PackageMaker as part of the Developer SDK to enable you to build packages.

In addition to copying resources to disparate parts of the file system, packages usually require administrator privileges. PackageMaker provides "out of the box" support for common installation steps such as obtaining admin privileges. You can add pre and post install scripts. Packages can be run remotely (and headlessly) to enable sys-admins to roll out applications and updates over the network with no user intervention.

Apple recommend a Manual Installer for applications which deliver their package to a single location - usually the /Applications directory. A Manual Installer is simply a DMG which contains an app. Apple don't provide any support to help you make the Manual Installer look nice. By default, you open the DMG and the app looks rather dead. I'm going to explain how many applications, such as Firefox make this a beautiful experience. I've also used the same methods to enhance the Phatch package installer DMG.

I mentioned earlier that sys-admins roll-out installs and updates across the network using packages. Because of the simple nature of a Manual Installer (it's a DMG which contains an app), it's easy to write a script to deploy Manual Installers in a 'headless' manner.

One of my tasks on the Phatch open source project was to create a Manual Installer. Our young Canadian team member André didn't want a package, because his parents won't grant him administrator privileges to the family computer. In fact, packages can elect to install at any location (such as ~/Applications) and don't have to have admin rights. However most packages request admin access.

I'd like to mention terrific work by Stani and Nadia. Phatch is a Python Script and uses many libraries which are not installed on the Mac by default. Stani and Nadia created Python.app with Py2app which converts phatch.py to the Phatch.app bundle. The bundle embeds all dependent libraries, scripts, fonts and other resources. Well done, Stani and Nadia.

I looked at Firefox and thought "how do they do that?". Mozilla deliver a DMG. When you open it - there's the application and the /Applications folder. You get a visual invitation to drag'n'drop the app to /Applications. You can copy the application anywhere you like. For that matter, you can double click the app and it will launch from the DMG. No installation required! And just for good measure, the DMG usually has a customized icon to "brand" the Volume while it's mounted.

Being a software guy, I googled around for something to borrow or steal. Here are some very useful sites:

  1. A Mozilla Paper about Reverse Engineering the .DS_Store file
    https://wiki.mozilla.org/DS_Store_File_Format
  2. A Perl Module based on the Mozilla paper
    I am very appreciative of Wim Lewis for doing this work. Thank you Wim.
    http://www.hhhh.org/src/hg/dsstore/
  3. How to create a custom dmg installer on mac
    PLOEM makes it sound easy, however I couldn't get his solution to work.
    http://www.ploem.be/blog/?page_id=26
  4. DMG Packager. This is a shareware product.
    This is pretty good and I can't remember why I didn't use it.
    http://www.macupdate.com/info.php/id/20882/dmg-packager

The most difficult part is to get the correct .DS_Store file into the read-only DMG that you deliver. This file defines the background to be used, the position and size of the App and /Applications icons on the display. The business of blessing the Volume with your custom Icon (and hiding some stuff) is handled by the SetFile utility provided by Apple in the /Developer/Applications/Utility folder.

You can obtain the .DS_Store file manually as follows:

  1. Use Disk Utility to create a read-write DMG that is big enough to hold everything you intend to store (for Phatch, that's a 100mb DMG). Call the file p1.dmg and call the Volume Phatch
    When he's created, he'll be in p1.dmg, however he'll mount as /Volumes/Phatch
  2. Open p1.dmg and add the contents. Drop in Phatch.app and an alias to /Applications.
  3. Add the background file background.png and use Finder/Show Options cmd-J to set it as the wallpaper.
    Be sure to specify /Volumes/Phatch/background.png
  4. Hide the background with the command SetFile -a V /Volumes/Phatch/background.png
  5. Add your custom icon .Volume.icns, and attach him to the Volume: SetFile -a C /Volumes/Phatch
  6. Arrange the appearance of /Volumes/Phatch to suit your aesthetic tastes.
  7. /Volumes/Phatch/.DS_Store is correctly stored in your DMG.
    Close p1.dmg and use Disk Utility to convert him to Phatch.dmg

I have scripted this process using the Perl Module "Mac::Finder::DSStore". You can obtain the Perl code from: http://www.hhhh.org/src/hg/dsstore/. I set the 32 bit flag to run the code on Snow Leopard (export VERSIONER_PERL_PREFER_32_BIT=yes). The code built and executed easily on Tiger (ppc) and Leopard (i386). The resulting DMGs work on 10.4 and above.

Here are links to the on-line documentation (PODs) that arrived with Mac::Finder::DSStore.

Title of documentLink
DSStoreFormatDSStoreFormat
Mac::Finder::DSStore.htmlMac-Finder-DSStore
Mac::Finder::DSStore::BuddyAllocator   Mac-Finder-DSStore-BuddyAllocator
Download code (latest version)  http://www.hhhh.org/src/hg/dsstore/
Download code (earlier version)  http://freehg.org/u/wiml/dsstore/

The overall strategy of the script is:

  1. Copy all the delivery files and folders to a tempory directory (called temp in the code)
    This lets me calculate the size of the DMG I need to create.
  2. Create a DMG and populate it with all the data and apps (called p1 in the code).
  3. Insert the correct .DS_Store into p1
  4. Close p1 and use hdiutil to create a compressed readonly copy. (called Phatch in the code)
    This is the one you'll want to distribute.

I'm sure you're wondering how I know the geometry of the solution that is buried in the Perl part of the script. I laid out the DMG manually and used the script examples/dsstore_dump.pl which came with the Mac::Finder::DSStore. Having reverse engineered the .DS_Store file, I was then included the definition in the script.

One of the nice parts of the Perl based solution is that it requires no manual intervention during the build process (my account has NOPASSWD in /etc/sudoers). If you want to do this without Perl installed, you can save p1.dmg. At update time, you can open p1.dmg and replace any of the files. (Don't open p1.dmg in the UI). Close p1.dmg and use hdiutil convert to create a new distributable Phatch.dmg.

#!/bin/bash

Volume=/Volumes/Phatch
if [ -e $Volume ]; then
    echo '--------------------------'
    echo Please unmount $Volume
    echo '--------------------------'
    sudo umount -f $Volume
    exit
fi

##
# setup some variables
temp=temp
p1=~/Desktop/p1.dmg
Phatch=~/Desktop/Phatch.dmg

##
# cleanup the mess from last time
rm -rf $p1
rm -rf $temp
rm -rf $Phatch

##
# create the temporary (source) directory
mkdir                             "$temp"
ditto universal/Phatch.app        "$temp/Phatch.app"
ditto background.png              "$temp/background.png"
ln    -s /Applications            "$temp/Applications"

##
# create and mount p1.dmg
dmgsize=(`du -m $temp | tail -1`)
dmgsize=$(( $dmgsize + 20 ))
hdiutil create -megabytes $dmgsize -fs HFS+J -volname Phatch $p1
open $p1
while [ ! -e /Volumes/Phatch ]; do
    sleep 1
done

##
# copy the stuff from the temporary directory to the Volume
cp -R $temp/*                       $Volume
cp  VolumeIcon.icns                 $Volume/.VolumeIcon.icns
/Developer/Tools/SetFile -a C       $Volume
/Developer/Tools/SetFile -a V       $Volume/background.png
rm -rf $temp

perl -x $0  # magic

##
# unmount the p1.pmg
sudo umount -f $Volume
while [ -e $Volume ]; do
    sleep 1
done
sleep 1

##
# make a compressed readonly copy of the DMG
hdiutil convert -format UDZO -o $Phatch $p1
rm -rf $p1

exit
# That's all Folks!
##

##################################################################
##
#!/opt/local/bin/perl -w

use Mac::Finder::DSStore qw( writeDSDBEntries makeEntries );
use Mac::Memory qw( );
use Mac::Files qw( NewAliasMinimal );
my $x = 200 ; 
my $y = 300 ;
my $w = 415 ;
my $h = 295 ;
&writeDSDBEntries("/Volumes/Phatch/.DS_Store",
    &makeEntries(".",
        BKGD_alias => NewAliasMinimal("/Volumes/Phatch/background.png"),
        ICVO => 1, 
        fwi0_flds => [ $y, $x, $y+$h, $x+$w, "icnv", 0, 0 ],
        fwsw => $w,
        fwvh => $h,
        icgo => "\0\0\0\0\0\0\0\4",
        icvo => pack('A4 n A4 A4 n*', "icv4", 108, "none", "botm", 0, 0, 4, 0, 4, 1, 0, 100, 1),
        icvt => 12,
        vstl => "icnv"
    ),
    &makeEntries(".DS_Store", Iloc_xy => [ 2, 2 ]),
    &makeEntries(".Trashes", Iloc_xy => [ 2, 2 ]),
    &makeEntries(".VolumeIcon.icns", Iloc_xy => [ 2, 2 ]),
    &makeEntries(".fseventsd", Iloc_xy => [ 2, 2 ]),
    &makeEntries("Applications", Iloc_xy => [ 291, 154 ]),
    &makeEntries("Phatch.app", Iloc_xy => [ 113, 152 ]),
    &makeEntries("background.png", Iloc_xy => [ 2, 2 ])
);

# That's all Folks!
##

Now if you came here for a "totally pain-free solution", I'm afraid you're out of luck. For sure the script could be streamlined and better packaged. I could build a fancy GUI from which everything is done painlessly. I'll leave these tasks to somebody else. Here's the link to my DMG: Phatch.dmg. Enjoy!

Comments?

I'm very happy to accept comments, feedback and suggestions for any of my articles. I'm always happy to hear you - especially if you have constructive suggestions. And I'm particularily pleased if you can let me know about corrections.


Home ......... About

Page design © 1996-2010 Robin Mills / webmaster@clanmills.com

Updated: Monday May 31, 2010