Create an installer package for a shell script
# "Package a single item for installation" open -a 'Help Viewer' /Developer/Applications/Utilities/PackageMaker.app/Contents/Resources/English.lproj/Help_Book/html/contents.html find /Library/Receipts -name "*.bom" | grep -i myapp || echo 'You can go on!' ls -l /usr/local/bin/hello.sh 2>/dev/null || echo 'You can go on!' # create a package directory structure to create an installer package for hello.sh function pkgdir() { # "Note: you will not need to make directories below Package_root that your package does not need." # see: http://www.osxgnu.org/info/osxpackages.html # (so in fact we could do without ...Package_Root/usr/bin, ...Package_Root/Applications and ...Package_Root/private/etc) : <<-'COMMENT' /bin/mkdir -p \ ~/Desktop/Distribution_folder/Package_Root/usr/local/bin \ ~/Desktop/Distribution_folder/Resources COMMENT /bin/mkdir -p \ ~/Desktop/Distribution_folder/Package_Root/usr/local/bin \ ~/Desktop/Distribution_folder/Package_Root/usr/bin \ ~/Desktop/Distribution_folder/Package_Root/Applications \ ~/Desktop/Distribution_folder/Package_Root/private/etc \ ~/Desktop/Distribution_folder/Resources # also create a directory where the installer package for hello.sh below will be created /bin/mkdir -p ~/Desktop/MyApp declare EX_MARK='!' /bin/cat > ~/Desktop/Distribution_folder/Resources/Info.plist <<-EOF <?xml version="1.0" encoding="UTF-8"?> <${EX_MARK}DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleGetInfoString</key> <string>hello-1.1.1</string> <key>CFBundleIdentifier</key> <string>org.myhome.myapp</string> <key>CFBundleName</key> <string>hello.sh</string> <key>CFBundleShortVersionString</key> <string>1.1.1</string> <key>IFPkgFlagAuthorizationAction</key> <string>RootAuthorization</string> <key>IFPkgFlagDefaultLocation</key> <string>/</string> <key>IFPkgFlagFollowLinks</key> <false/> <key>IFPkgFlagRootVolumeOnly</key> <true/> </dict> </plist> EOF /bin/cat > ~/Desktop/Distribution_folder/Resources/Welcome.txt <<-'EOF' This is my welcome message! EOF /bin/cat > ~/Desktop/Distribution_folder/Resources/License.txt <<-'EOF' The MIT License EOF /bin/cat > ~/Desktop/Distribution_folder/Resources/ReadMe.txt <<-'EOF' Read me! EOF # hello.sh /bin/cat > ~/Desktop/Distribution_folder/Package_Root/usr/local/bin/hello.sh <<-'EOF' #!/bin/bash echo hello exit 0 EOF # postflight # taken from: open /Developer/Applications/Utilities/PackageMaker.app/Contents/Resources/English.lproj/Help_Book/package/scripts.html /bin/cat > ~/Desktop/Distribution_folder/Resources/postflight <<-'EOF' #!/bin/bash # # This postflight script echoes the values of the available # arguments and environmental variables. # /usr/bin/logger -i "Start postflight script" /usr/bin/logger -i "" /usr/bin/logger -i "Arguments:" /usr/bin/logger -i "" /usr/bin/logger -i "\$1: full path to the installation package" /usr/bin/logger -i " $1" /usr/bin/logger -i "\$2: full path to the installation destination" /usr/bin/logger -i " $2" /usr/bin/logger -i "\$3: mountpoint of the destination volume" /usr/bin/logger -i " $3" /usr/bin/logger -i "\$4: root directory \"/\" for the current System folder" /usr/bin/logger -i " $4" /usr/bin/logger -i "" /usr/bin/logger -i "Environment variables available to a postflight executable:" /usr/bin/logger -i " INSTALLER_TEMP, PACKAGE_PATH, RECEIPT_PATH, SCRIPT_NAME, and TMPDIR" /usr/bin/logger -i "" /usr/bin/logger -i "\$INSTALLER_TEMP: scratch area used by Installer for temporary work files" /usr/bin/logger -i " $INSTALLER_TEMP" /usr/bin/logger -i "" /usr/bin/logger -i "\$PACKAGE_PATH: full path to the installation package; should be same as \$1" /usr/bin/logger -i " $PACKAGE_PATH" /usr/bin/logger -i "" /usr/bin/logger -i "\$RECEIPT_PATH: full path to directory containing the file being executed" /usr/bin/logger -i " $RECEIPT_PATH" /usr/bin/logger -i "" /usr/bin/logger -i "\$SCRIPT_NAME: name of the file being executed" /usr/bin/logger -i " $SCRIPT_NAME" /usr/bin/logger -i "" /usr/bin/logger -i "\$TMPDIR: if set, a path to a location on a writable destination volume" /usr/bin/logger -i " $TMPDIR" /usr/bin/logger -i "" /usr/bin/logger -i "End postflight script" exit 0 EOF # convert .txt to .rtf files /usr/bin/textutil -convert rtf ~/Desktop/Distribution_folder/Resources/*.txt # remove .txt files /bin/rm -f \ ~/Desktop/Distribution_folder/Resources/Welcome.txt \ ~/Desktop/Distribution_folder/Resources/License.txt \ ~/Desktop/Distribution_folder/Resources/ReadMe.txt # set permissions & ownership /bin/chmod -R 755 ~/Desktop/Distribution_folder/* /usr/bin/find -x ~/Desktop/Distribution_folder -type f \( -name "*.rtf" -or -name "*.plist" \) -print0 | \ /usr/bin/xargs -0 /bin/chmod 644 /usr/bin/sudo /usr/sbin/chown -R root:wheel ~/Desktop/Distribution_folder/* /usr/bin/sudo /usr/sbin/chown -R root:admin ~/Desktop/Distribution_folder/Package_Root/Applications # make sure 'sudo' can only be used with a password again # man sudo | less -p timestamp # man sudo | less -p '5 minutes' /usr/bin/sudo -k echo printf "\e[1m%s\e[m\n" 'Permissions & ownership:' #/usr/bin/find -x ~/Desktop/Distribution_folder -ls /usr/bin/find -x ~/Desktop/Distribution_folder -print0 | /usr/bin/xargs -0 /bin/ls -ldG return 0 } function uninstall_hello.sh() { /usr/bin/sudo /bin/rm -Rf /Library/Receipts/MyApp.pkg /usr/bin/sudo /bin/rm -f /usr/local/bin/hello.sh /usr/bin/sudo -k return 0 } pkgdir /usr/bin/sudo /bin/ln -is /Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker /usr/local/bin/packagemaker /usr/bin/sudo -k packagemaker -help # create the package in ~/Desktop/MyApp /usr/local/bin/packagemaker -build -ds \ -p ~/Desktop/MyApp/MyApp.pkg \ -f ~/Desktop/Distribution_folder/Package_Root \ -r ~/Desktop/Distribution_folder/Resources \ -i ~/Desktop/Distribution_folder/Resources/Info.plist /usr/bin/find -x ~/Desktop/MyApp/MyApp.pkg -print0 | /usr/bin/xargs -0 /bin/ls -ldG lsbom -s ~/Desktop/MyApp/MyApp.pkg/Contents/Archive.bom lsbom -p MUGsf ~/Desktop/MyApp/MyApp.pkg/Contents/Archive.bom lsbom -f -l -p f ~/Desktop/MyApp/MyApp.pkg/Contents/Archive.bom # install MyApp.pkg open -a Installer ~/Desktop/MyApp/MyApp.pkg #/usr/bin/sudo /usr/sbin/installer -pkg ~/Desktop/MyApp/MyApp.pkg -target "/" # postflight script output open -a Console /var/log/system.log ls -l /usr/local/bin/hello.sh find /Library/Receipts -name "*.bom" | grep -i myapp lsbom -s /Library/Receipts/MyApp.pkg/Contents/Archive.bom lsbom -s /Library/Receipts/MyApp.pkg/Contents/Resources/MyApp.bom lsbom -p MUGsf /Library/Receipts/MyApp.pkg/Contents/Archive.bom lsbom -p MUGsf /Library/Receipts/MyApp.pkg/Contents/Resources/MyApp.bom lsbom -f -l -p f /Library/Receipts/MyApp.pkg/Contents/Archive.bom lsbom -f -l -p f /Library/Receipts/MyApp.pkg/Contents/Resources/MyApp.bom # get ownership/permissions like in a clean install #lsbom -p MUGsf /Library/Receipts/BaseSystem.pkg/Contents/Archive.bom # restore ownership/permissions like in a clean install #/usr/bin/sudo /usr/sbin/diskutil repairPermissions / /usr/local/bin/hello.sh uninstall_hello.sh
Further information:
- man packagemaker
- man pkgutil
- Installing Your Application on Mac OS X: Guidelines for Developers
- Software Delivery Guide
- Software Distribution Legacy Guide
- PackageMaker User Guide
- PackageMaker tips (2008)
- PackageMaker 3.0 (2007)
- Mac Software Packaging Utilities List (2007)
- Distributing with PackageMaker (2006)
- Xcode for the Rest of Us (2006)
- PackageMaker How-to (2006)
- Beware of Installers bearing packages (2005)
- Building Installer.app packages (2005)
- Creating MAC OSX packages for distribution
- Suspicious Package (software)