├── .editorconfig ├── .gitignore ├── .this-is-the-create-dmg-repo ├── LICENSE ├── Makefile ├── README.md ├── builder └── create-dmg.builder ├── create-dmg ├── doc-project ├── Developer Notes.md └── Release Checklist.md ├── examples └── 01-main-example │ ├── installer_background.png │ ├── sample │ └── source_folder │ └── Application.app ├── support ├── eula-resources-template.xml └── template.applescript └── tests └── 007-space-in-dir-name ├── my files └── hello.txt └── run-test /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig for create-dmg project 2 | # EditorConfig is awesome: https://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | 11 | # We use tabs in our own code 12 | [{create-dmg,*.applescript,*.sh}] 13 | indent_style = tab 14 | indent_size = 2 15 | 16 | # But the Python code we pull in from pyhacker uses spaces 17 | [*.py] 18 | indent_style = space 19 | indent_size = 4 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .svn 2 | .vscode 3 | 4 | *.dmg 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.this-is-the-create-dmg-repo: -------------------------------------------------------------------------------- 1 | This is just a dummy file so create-dmg can tell whether it's being run from 2 | inside the Git repo or from an installed location. 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2008-2014 Andrey Tarantsov 4 | Copyright (c) 2020 Andrew Janke 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Cowsay 2 | 3 | PACKAGE_TARNAME = create-dmg 4 | 5 | prefix = /usr/local 6 | exec_prefix = ${prefix} 7 | bindir = ${exec_prefix}/bin 8 | datarootdir = ${prefix}/share 9 | datadir = ${datarootdir} 10 | docdir = ${datarootdir}/doc/${PACKAGE_TARNAME} 11 | sysconfdir = ${prefix}/etc 12 | mandir=${datarootdir}/man 13 | srcdir = . 14 | 15 | SHELL = /bin/sh 16 | INSTALL = install 17 | INSTALL_PROGRAM = $(INSTALL) 18 | INSTALL_DATA = ${INSTALL} -m 644 19 | 20 | .PHONY: install uninstall 21 | 22 | install: create-dmg 23 | $(INSTALL) -d $(DESTDIR)$(prefix) 24 | $(INSTALL) -d $(DESTDIR)$(bindir) 25 | $(INSTALL_PROGRAM) create-dmg $(DESTDIR)$(bindir)/create-dmg 26 | $(INSTALL) -d $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) 27 | cp -R support $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) 28 | cp -R examples $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) 29 | cp -R tests $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) 30 | 31 | uninstall: 32 | rm -f $(DESTDIR)$(bindir)/create-dmg 33 | rm -rf $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | create-dmg 2 | ========== 3 | 4 | A shell script to build fancy DMGs. 5 | 6 | Status and contribution policy 7 | ------------------------------ 8 | 9 | Create-dmg is mostly maintained by [@aonez](https://github.com/aonez) and the contributors who send pull requests. 10 | The project home page is . 11 | 12 | We will merge any pull request that adds something useful and does not break existing things. 13 | 14 | If you're an active user and want to be a maintainer, or just want to chat, please ping us on Gitter at [gitter.im/create-dmg/Lobby](https://gitter.im/create-dmg/Lobby), or [email Andrew directly](floss@apjanke.net). 15 | 16 | Create-dmg was originally created by [Andrey Tarantsov](https://github.com/andreyvit). 17 | In May 2020 [Andrew Janke](https://github.com/apjanke) helped vastly with the project. 18 | 19 | Installation 20 | ------------ 21 | 22 | - You can install this script using [Homebrew](https://brew.sh): 23 | 24 | ```sh 25 | brew install create-dmg 26 | ``` 27 | 28 | - You can download the [latest release](https://github.com/create-dmg/create-dmg/releases/latest) and install it from there: 29 | 30 | ```sh 31 | make install 32 | ``` 33 | 34 | - You can also clone the entire repository and run it locally from there: 35 | 36 | ```sh 37 | git clone https://github.com/create-dmg/create-dmg.git 38 | ``` 39 | 40 | Usage 41 | ----- 42 | 43 | ```sh 44 | create-dmg [options ...] 45 | ``` 46 | 47 | All contents of source\_folder will be copied into the disk image. 48 | 49 | **Options:** 50 | 51 | - **--volname \:** set volume name (displayed in the Finder sidebar and window title) 52 | - **--volicon \:** set volume icon 53 | - **--background \:** set folder background image (provide png, gif, jpg) 54 | - **--window-pos \ \:** set position the folder window 55 | - **--window-size \ \:** set size of the folder window 56 | - **--text-size \:** set window text size (10-16) 57 | - **--icon-size \:** set window icons size (up to 128) 58 | - **--icon \ \ \:** set position of the file's icon 59 | - **--hide-extension \:** hide the extension of file 60 | - **--custom-icon \ \ \:** set position and -tom icon 61 | - **--app-drop-link \ \:** make a drop link to Applications, at location x, y 62 | - **--ql-drop-link \ \:** make a drop link to /Library/QuickLook, at location x, y 63 | - **--eula \:** attach a license file to the dmg 64 | - **--rez \:** specify custom path to Rez tool used to include license file 65 | - **--no-internet-enable:** disable automatic mount© 66 | - **--format:** specify the final image format (UDZO|UDBZ|ULFO|ULMO) (default is UDZO) 67 | - **--add-file \ \ \ \:** add additional file or folder (can be used multiple times) 68 | - **--disk-image-size \:** set the disk image size manually to x MB 69 | - **--hdiutil-verbose:** execute hdiutil in verbose mode 70 | - **--hdiutil-quiet:** execute hdiutil in quiet mode 71 | - **--bless:** bless the mount folder (deprecated, needs macOS 12.2.1 or older, [#127](https://github.com/create-dmg/create-dmg/pull/127)) 72 | - **--codesign \:** codesign the disk image with the specified signature 73 | - **--notarize \:** notarize the disk image (waits and staples) with the keychain stored credentials 74 | For more information check [Apple's documentation](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow) 75 | - **--skip-jenkins:** skip Finder-prettifying AppleScript, useful in Sandbox and non-GUI environments, [#72](https://github.com/create-dmg/create-dmg/pull/72) 76 | - **--sandbox-safe:** hdiutil with sandbox compatibility, do not bless and do not execute the cosmetic AppleScript 77 | - **--version:** show tool version number 78 | - **-h, --help:** display the help 79 | 80 | Example 81 | ------- 82 | 83 | ```sh 84 | #!/bin/sh 85 | test -f Application-Installer.dmg && rm Application-Installer.dmg 86 | create-dmg \ 87 | --volname "Application Installer" \ 88 | --volicon "application_icon.icns" \ 89 | --background "installer_background.png" \ 90 | --window-pos 200 120 \ 91 | --window-size 800 400 \ 92 | --icon-size 100 \ 93 | --icon "Application.app" 200 190 \ 94 | --hide-extension "Application.app" \ 95 | --app-drop-link 600 185 \ 96 | "Application-Installer.dmg" \ 97 | "source_folder/" 98 | ``` 99 | 100 | See the `examples` folder in the source tree for more examples. 101 | 102 | Requirements 103 | ------------ 104 | 105 | Nothing except a standard installation of macOS/OS X is required. 106 | 107 | We think this works in OS X 10.6 Snow Leopard and later. 108 | 109 | We'd like to keep it working in as many versions as possible, but unfortunately, we just don't have test boxes running old versions of OS X adequate to make this happen. Development and testing mostly happens in the last 3-5 years' worth of macOS releases; as of 2020, this means macOS 10.12 and later. 110 | 111 | But if you find a bug in an older version, go ahead and report it! We'll try to work with you to get it fixed. 112 | 113 | If you're running OS X 10.5 or later, you're SOL. That's just too hard to deal with in 2020. ;) 114 | 115 | Alternatives 116 | ------------ 117 | 118 | - [node-appdmg](https://github.com/LinusU/node-appdmg) 119 | - [dmgbuild](https://pypi.python.org/pypi/dmgbuild) 120 | - see the [StackOverflow question](http://stackoverflow.com/questions/96882/how-do-i-create-a-nice-looking-dmg-for-mac-os-x-using-command-line-tools) 121 | -------------------------------------------------------------------------------- /builder/create-dmg.builder: -------------------------------------------------------------------------------- 1 | SET app_name create-dmg 2 | 3 | VERSION create-dmg.cur create-dmg heads/master 4 | 5 | NEWDIR build.dir temp %-build - 6 | 7 | NEWFILE create-dmg.zip featured %.zip % 8 | 9 | 10 | COPYTO [build.dir] 11 | INTO create-dmg [create-dmg.cur]/create-dmg 12 | INTO sample [create-dmg.cur]/sample 13 | INTO support [create-dmg.cur]/support 14 | 15 | SUBSTVARS [build.dir]/create-dmg [[]] 16 | 17 | 18 | ZIP [create-dmg.zip] 19 | INTO [build-files-prefix] [build.dir] 20 | 21 | 22 | PUT megabox-builds create-dmg.zip 23 | PUT megabox-builds build.log 24 | 25 | PUT s3-builds create-dmg.zip 26 | PUT s3-builds build.log 27 | -------------------------------------------------------------------------------- /create-dmg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Create a read-only disk image of the contents of a folder 4 | 5 | # Bail out on any unhandled errors 6 | set -e; 7 | # Any command that exits with non-zero code will cause the pipeline to fail 8 | set -o pipefail; 9 | 10 | CDMG_VERSION='1.1.1' 11 | 12 | # The full path to the "support/" directory this script is using 13 | # (This will be set up by code later in the script.) 14 | CDMG_SUPPORT_DIR="" 15 | 16 | OS_FULL_VERSION="$(sw_vers | sed -n 2p | cut -d : -f 2 | tr -d '[:space:]' | cut -c1-)" 17 | OS_MAJOR_VERSION="$(echo $OS_FULL_VERSION | cut -d . -f 1)" 18 | OS_MINOR_VERSION="$(echo $OS_FULL_VERSION | cut -d . -f 2)" 19 | WINX=10 20 | WINY=60 21 | WINW=500 22 | WINH=350 23 | ICON_SIZE=128 24 | TEXT_SIZE=16 25 | FORMAT="UDZO" 26 | ADD_FILE_SOURCES=() 27 | ADD_FILE_TARGETS=() 28 | IMAGEKEY="" 29 | HDIUTIL_VERBOSITY="" 30 | SANDBOX_SAFE=0 31 | BLESS=0 32 | SKIP_JENKINS=0 33 | MAXIMUM_UNMOUNTING_ATTEMPTS=3 34 | SIGNATURE="" 35 | NOTARIZE="" 36 | 37 | function pure_version() { 38 | echo "$CDMG_VERSION" 39 | } 40 | 41 | function hdiutil_detach_retry() { 42 | # Unmount 43 | unmounting_attempts=0 44 | until 45 | echo "Unmounting disk image..." 46 | (( unmounting_attempts++ )) 47 | hdiutil detach "$1" 48 | exit_code=$? 49 | (( exit_code == 0 )) && break # nothing goes wrong 50 | (( exit_code != 16 )) && exit $exit_code # exit with the original exit code 51 | # The above statement returns 1 if test failed (exit_code == 16). 52 | # It can make the code in the {do... done} block to be executed 53 | do 54 | (( unmounting_attempts == MAXIMUM_UNMOUNTING_ATTEMPTS )) && exit 16 # patience exhausted, exit with code EBUSY 55 | echo "Wait a moment..." 56 | sleep $(( 1 * (2 ** unmounting_attempts) )) 57 | done 58 | unset unmounting_attempts 59 | } 60 | 61 | function version() { 62 | echo "create-dmg $(pure_version)" 63 | } 64 | 65 | function usage() { 66 | version 67 | cat < 72 | 73 | All contents of will be copied into the disk image. 74 | 75 | Options: 76 | --volname 77 | set volume name (displayed in the Finder sidebar and window title) 78 | --volicon 79 | set volume icon 80 | --background 81 | set folder background image (provide png, gif, or jpg) 82 | --window-pos 83 | set position the folder window 84 | --window-size 85 | set size of the folder window 86 | --text-size 87 | set window text size (10-16) 88 | --icon-size 89 | set window icons size (up to 128) 90 | --icon file_name 91 | set position of the file's icon 92 | --hide-extension 93 | hide the extension of file 94 | --app-drop-link 95 | make a drop link to Applications, at location x,y 96 | --ql-drop-link 97 | make a drop link to user QuickLook install dir, at location x,y 98 | --eula 99 | attach a license file to the dmg (plain text or RTF) 100 | --no-internet-enable 101 | disable automatic mount & copy 102 | --format 103 | specify the final disk image format (UDZO|UDBZ|ULFO|ULMO) (default is UDZO) 104 | --add-file | 105 | add additional file or folder (can be used multiple times) 106 | --disk-image-size 107 | set the disk image size manually to x MB 108 | --hdiutil-verbose 109 | execute hdiutil in verbose mode 110 | --hdiutil-quiet 111 | execute hdiutil in quiet mode 112 | --bless 113 | bless the mount folder (deprecated, needs macOS 12.2.1 or older) 114 | --codesign 115 | codesign the disk image with the specified signature 116 | --notarize 117 | notarize the disk image (waits and staples) with the keychain stored credentials 118 | --sandbox-safe 119 | execute hdiutil with sandbox compatibility and do not bless 120 | --version 121 | show create-dmg version number 122 | -h, --help 123 | display this help screen 124 | 125 | EOHELP 126 | exit 0 127 | } 128 | 129 | # Argument parsing 130 | 131 | while [[ "${1:0:1}" = "-" ]]; do 132 | case $1 in 133 | --volname) 134 | VOLUME_NAME="$2" 135 | shift; shift;; 136 | --volicon) 137 | VOLUME_ICON_FILE="$2" 138 | shift; shift;; 139 | --background) 140 | BACKGROUND_FILE="$2" 141 | BACKGROUND_FILE_NAME="$(basename "$BACKGROUND_FILE")" 142 | BACKGROUND_CLAUSE="set background picture of opts to file \".background:$BACKGROUND_FILE_NAME\"" 143 | REPOSITION_HIDDEN_FILES_CLAUSE="set position of every item to {theBottomRightX + 100, 100}" 144 | shift; shift;; 145 | --icon-size) 146 | ICON_SIZE="$2" 147 | shift; shift;; 148 | --text-size) 149 | TEXT_SIZE="$2" 150 | shift; shift;; 151 | --window-pos) 152 | WINX=$2; WINY=$3 153 | shift; shift; shift;; 154 | --window-size) 155 | WINW=$2; WINH=$3 156 | shift; shift; shift;; 157 | --icon) 158 | POSITION_CLAUSE="${POSITION_CLAUSE}set position of item \"$2\" to {$3, $4} 159 | " 160 | shift; shift; shift; shift;; 161 | --hide-extension) 162 | HIDING_CLAUSE="${HIDING_CLAUSE}set the extension hidden of item \"$2\" to true 163 | " 164 | shift; shift;; 165 | -h | --help) 166 | usage;; 167 | --version) 168 | version; exit 0;; 169 | --pure-version) 170 | pure_version; exit 0;; 171 | --ql-drop-link) 172 | QL_LINK=$2 173 | QL_CLAUSE="set position of item \"QuickLook\" to {$2, $3} 174 | " 175 | shift; shift; shift;; 176 | --app-drop-link) 177 | APPLICATION_LINK=$2 178 | APPLICATION_CLAUSE="set position of item \"Applications\" to {$2, $3} 179 | " 180 | shift; shift; shift;; 181 | --eula) 182 | EULA_RSRC=$2 183 | shift; shift;; 184 | --no-internet-enable) 185 | NOINTERNET=1 186 | shift;; 187 | --format) 188 | FORMAT="$2" 189 | shift; shift;; 190 | --add-file | --add-folder) 191 | ADD_FILE_TARGETS+=("$2") 192 | ADD_FILE_SOURCES+=("$3") 193 | POSITION_CLAUSE="${POSITION_CLAUSE} 194 | set position of item \"$2\" to {$4, $5} 195 | " 196 | shift; shift; shift; shift; shift;; 197 | --disk-image-size) 198 | DISK_IMAGE_SIZE="$2" 199 | shift; shift;; 200 | --hdiutil-verbose) 201 | HDIUTIL_VERBOSITY='-verbose' 202 | shift;; 203 | --hdiutil-quiet) 204 | HDIUTIL_VERBOSITY='-quiet' 205 | shift;; 206 | --codesign) 207 | SIGNATURE="$2" 208 | shift; shift;; 209 | --notarize) 210 | NOTARIZE="$2" 211 | shift; shift;; 212 | --sandbox-safe) 213 | SANDBOX_SAFE=1 214 | shift;; 215 | --bless) 216 | BLESS=1 217 | shift;; 218 | --rez) 219 | echo "REZ is no more directly used. You can remove the --rez argument." 220 | shift; shift;; 221 | --skip-jenkins) 222 | SKIP_JENKINS=1 223 | shift;; 224 | -*) 225 | echo "Unknown option: $1. Run 'create-dmg --help' for help." 226 | exit 1;; 227 | esac 228 | case $FORMAT in 229 | UDZO) 230 | IMAGEKEY="-imagekey zlib-level=9";; 231 | UDBZ) 232 | IMAGEKEY="-imagekey bzip2-level=9";; 233 | ULFO) 234 | ;; 235 | ULMO) 236 | ;; 237 | *) 238 | echo >&2 "Unknown disk image format: $FORMAT" 239 | exit 1;; 240 | esac 241 | done 242 | 243 | if [[ -z "$2" ]]; then 244 | echo "Not enough arguments. Run 'create-dmg --help' for help." 245 | exit 1 246 | fi 247 | 248 | DMG_PATH="$1" 249 | SRC_FOLDER="$(cd "$2" > /dev/null; pwd)" 250 | 251 | # Argument validation checks 252 | 253 | if [[ "${DMG_PATH: -4}" != ".dmg" ]]; then 254 | echo "Output file name must end with a .dmg extension. Run 'create-dmg --help' for help." 255 | exit 1 256 | fi 257 | 258 | # Main script logic 259 | 260 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 261 | DMG_DIRNAME="$(dirname "$DMG_PATH")" 262 | DMG_DIR="$(cd "$DMG_DIRNAME" > /dev/null; pwd)" 263 | DMG_NAME="$(basename "$DMG_PATH")" 264 | DMG_TEMP_NAME="$DMG_DIR/rw.${DMG_NAME}" 265 | 266 | # Detect where we're running from 267 | 268 | sentinel_file="$SCRIPT_DIR/.this-is-the-create-dmg-repo" 269 | if [[ -f "$sentinel_file" ]]; then 270 | # We're running from inside a repo 271 | CDMG_SUPPORT_DIR="$SCRIPT_DIR/support" 272 | else 273 | # We're running inside an installed location 274 | bin_dir="$SCRIPT_DIR" 275 | prefix_dir=$(dirname "$bin_dir") 276 | CDMG_SUPPORT_DIR="$prefix_dir/share/create-dmg/support" 277 | fi 278 | 279 | if [[ -z "$VOLUME_NAME" ]]; then 280 | VOLUME_NAME="$(basename "$DMG_PATH" .dmg)" 281 | fi 282 | 283 | if [[ ! -d "$CDMG_SUPPORT_DIR" ]]; then 284 | echo >&2 "Cannot find support/ directory: expected at: $CDMG_SUPPORT_DIR" 285 | exit 1 286 | fi 287 | 288 | # Create the image 289 | echo "Creating disk image..." 290 | if [[ -f "${DMG_TEMP_NAME}" ]]; then 291 | rm -f "${DMG_TEMP_NAME}" 292 | fi 293 | 294 | # Use Megabytes since hdiutil fails with very large byte numbers 295 | function blocks_to_megabytes() { 296 | # Add 1 extra MB, since there's no decimal retention here 297 | MB_SIZE=$((($1 * 512 / 1000 / 1000) + 1)) 298 | echo $MB_SIZE 299 | } 300 | 301 | function get_size() { 302 | # Get block size in disk 303 | if [[ $OS_MAJOR_VERSION -ge 12 ]]; then 304 | bytes_size=$(du -B 512 -s "$1") 305 | else 306 | bytes_size=$(du -s "$1") 307 | fi 308 | bytes_size=$(echo $bytes_size | sed -e 's/ .*//g') 309 | echo $(blocks_to_megabytes $bytes_size) 310 | } 311 | 312 | # Create the DMG with the specified size or the hdiutil estimation 313 | CUSTOM_SIZE='' 314 | if [[ -n "$DISK_IMAGE_SIZE" ]]; then 315 | CUSTOM_SIZE="-size ${DISK_IMAGE_SIZE}m" 316 | fi 317 | 318 | if [[ $SANDBOX_SAFE -eq 0 ]]; then 319 | hdiutil create ${HDIUTIL_VERBOSITY} -srcfolder "$SRC_FOLDER" -volname "${VOLUME_NAME}" \ 320 | -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW ${CUSTOM_SIZE} "${DMG_TEMP_NAME}" 321 | else 322 | hdiutil makehybrid ${HDIUTIL_VERBOSITY} -default-volume-name "${VOLUME_NAME}" -hfs -o "${DMG_TEMP_NAME}" "$SRC_FOLDER" 323 | hdiutil convert -format UDRW -ov -o "${DMG_TEMP_NAME}" "${DMG_TEMP_NAME}" 324 | DISK_IMAGE_SIZE_CUSTOM=$DISK_IMAGE_SIZE 325 | fi 326 | 327 | # Get the created DMG actual size 328 | DISK_IMAGE_SIZE=$(get_size "${DMG_TEMP_NAME}") 329 | 330 | # Use the custom size if bigger 331 | if [[ $SANDBOX_SAFE -eq 1 ]] && [[ ! -z "$DISK_IMAGE_SIZE_CUSTOM" ]] && [[ $DISK_IMAGE_SIZE_CUSTOM -gt $DISK_IMAGE_SIZE ]]; then 332 | DISK_IMAGE_SIZE=$DISK_IMAGE_SIZE_CUSTOM 333 | fi 334 | 335 | # Estimate the additional sources size 336 | if [[ -n "$ADD_FILE_SOURCES" ]]; then 337 | for i in "${!ADD_FILE_SOURCES[@]}"; do 338 | SOURCE_SIZE=$(get_size "${ADD_FILE_SOURCES[$i]}") 339 | DISK_IMAGE_SIZE=$(expr $DISK_IMAGE_SIZE + $SOURCE_SIZE) 340 | done 341 | fi 342 | 343 | # Add extra space for additional resources 344 | DISK_IMAGE_SIZE=$(expr $DISK_IMAGE_SIZE + 20) 345 | 346 | # Make sure target image size is within limits 347 | MIN_DISK_IMAGE_SIZE=$(hdiutil resize -limits "${DMG_TEMP_NAME}" | awk 'NR=1{print int($1/2048+1)}') 348 | if [ $MIN_DISK_IMAGE_SIZE -gt $DISK_IMAGE_SIZE ]; then 349 | DISK_IMAGE_SIZE=$MIN_DISK_IMAGE_SIZE 350 | fi 351 | 352 | # Resize the image for the extra stuff 353 | hdiutil resize ${HDIUTIL_VERBOSITY} -size ${DISK_IMAGE_SIZE}m "${DMG_TEMP_NAME}" 354 | 355 | # Mount the new DMG 356 | 357 | MOUNT_DIR="/Volumes/${VOLUME_NAME}" 358 | 359 | # Unmount leftover dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it) 360 | if [[ -d "${MOUNT_DIR}" ]]; then 361 | echo "Unmounting old disk image from $MOUNT_DIR..." 362 | DEV_NAME=$(hdiutil info | grep -E --color=never '^/dev/' | sed 1q | awk '{print $1}') 363 | hdiutil_detach_retry "${DEV_NAME}" 364 | fi 365 | 366 | echo "Mounting disk image..." 367 | 368 | echo "Mount directory: $MOUNT_DIR" 369 | DEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | grep -E --color=never '^/dev/' | sed 1q | awk '{print $1}') 370 | echo "Device name: $DEV_NAME" 371 | 372 | if [[ -n "$BACKGROUND_FILE" ]]; then 373 | echo "Copying background file '$BACKGROUND_FILE'..." 374 | [[ -d "$MOUNT_DIR/.background" ]] || mkdir "$MOUNT_DIR/.background" 375 | cp "$BACKGROUND_FILE" "$MOUNT_DIR/.background/$BACKGROUND_FILE_NAME" 376 | fi 377 | 378 | if [[ -n "$APPLICATION_LINK" ]]; then 379 | echo "Making link to Applications dir..." 380 | echo $MOUNT_DIR 381 | ln -s /Applications "$MOUNT_DIR/Applications" 382 | fi 383 | 384 | if [[ -n "$QL_LINK" ]]; then 385 | echo "Making link to QuickLook install dir..." 386 | echo $MOUNT_DIR 387 | ln -s "/Library/QuickLook" "$MOUNT_DIR/QuickLook" 388 | fi 389 | 390 | if [[ -n "$VOLUME_ICON_FILE" ]]; then 391 | echo "Copying volume icon file '$VOLUME_ICON_FILE'..." 392 | cp "$VOLUME_ICON_FILE" "$MOUNT_DIR/.VolumeIcon.icns" 393 | SetFile -c icnC "$MOUNT_DIR/.VolumeIcon.icns" 394 | fi 395 | 396 | if [[ -n "$ADD_FILE_SOURCES" ]]; then 397 | echo "Copying custom files..." 398 | for i in "${!ADD_FILE_SOURCES[@]}"; do 399 | echo "${ADD_FILE_SOURCES[$i]}" 400 | cp -a "${ADD_FILE_SOURCES[$i]}" "$MOUNT_DIR/${ADD_FILE_TARGETS[$i]}" 401 | done 402 | fi 403 | 404 | # Run AppleScript to do all the Finder cosmetic stuff 405 | APPLESCRIPT_FILE=$(mktemp -t createdmg.tmp.XXXXXXXXXX) 406 | if [[ $SANDBOX_SAFE -eq 1 ]]; then 407 | echo "Skipping Finder-prettifying AppleScript because we are in Sandbox..." 408 | else 409 | if [[ $SKIP_JENKINS -eq 0 ]]; then 410 | cat "$CDMG_SUPPORT_DIR/template.applescript" \ 411 | | sed -e "s/WINX/$WINX/g" -e "s/WINY/$WINY/g" -e "s/WINW/$WINW/g" \ 412 | -e "s/WINH/$WINH/g" -e "s/BACKGROUND_CLAUSE/$BACKGROUND_CLAUSE/g" \ 413 | -e "s/REPOSITION_HIDDEN_FILES_CLAUSE/$REPOSITION_HIDDEN_FILES_CLAUSE/g" \ 414 | -e "s/ICON_SIZE/$ICON_SIZE/g" -e "s/TEXT_SIZE/$TEXT_SIZE/g" \ 415 | | perl -pe "s/POSITION_CLAUSE/$POSITION_CLAUSE/g" \ 416 | | perl -pe "s/QL_CLAUSE/$QL_CLAUSE/g" \ 417 | | perl -pe "s/APPLICATION_CLAUSE/$APPLICATION_CLAUSE/g" \ 418 | | perl -pe "s/HIDING_CLAUSE/$HIDING_CLAUSE/" \ 419 | > "$APPLESCRIPT_FILE" 420 | sleep 2 # pause to workaround occasional "Can’t get disk" (-1728) issues 421 | echo "Running AppleScript to make Finder stuff pretty: /usr/bin/osascript \"${APPLESCRIPT_FILE}\" \"${VOLUME_NAME}\"" 422 | if /usr/bin/osascript "${APPLESCRIPT_FILE}" "${VOLUME_NAME}"; then 423 | # Okay, we're cool 424 | true 425 | else 426 | echo >&2 "Failed running AppleScript" 427 | hdiutil_detach_retry "${DEV_NAME}" 428 | exit 64 429 | fi 430 | echo "Done running the AppleScript..." 431 | sleep 4 432 | rm "$APPLESCRIPT_FILE" 433 | fi 434 | fi 435 | 436 | # Make sure it's not world writeable 437 | echo "Fixing permissions..." 438 | chmod -Rf go-w "${MOUNT_DIR}" &> /dev/null || true 439 | echo "Done fixing permissions" 440 | 441 | # Make the top window open itself on mount: 442 | if [[ $BLESS -eq 1 && $SANDBOX_SAFE -eq 0 ]]; then 443 | echo "Blessing started" 444 | if [ $(uname -m) == "arm64" ]; then 445 | bless --folder "${MOUNT_DIR}" 446 | else 447 | bless --folder "${MOUNT_DIR}" --openfolder "${MOUNT_DIR}" 448 | fi 449 | echo "Blessing finished" 450 | else 451 | echo "Skipping blessing on sandbox" 452 | fi 453 | 454 | if [[ -n "$VOLUME_ICON_FILE" ]]; then 455 | # Tell the volume that it has a special file attribute 456 | SetFile -a C "$MOUNT_DIR" 457 | fi 458 | 459 | # Delete unnecessary file system events log if possible 460 | echo "Deleting .fseventsd" 461 | rm -rf "${MOUNT_DIR}/.fseventsd" || true 462 | 463 | hdiutil_detach_retry "${DEV_NAME}" 464 | 465 | # Compress image 466 | echo "Compressing disk image..." 467 | hdiutil convert ${HDIUTIL_VERBOSITY} "${DMG_TEMP_NAME}" -format ${FORMAT} ${IMAGEKEY} -o "${DMG_DIR}/${DMG_NAME}" 468 | rm -f "${DMG_TEMP_NAME}" 469 | 470 | # Adding EULA resources 471 | if [[ -n "${EULA_RSRC}" && "${EULA_RSRC}" != "-null-" ]]; then 472 | echo "Adding EULA resources..." 473 | # 474 | # Use udifrez instead flatten/rez/unflatten 475 | # https://github.com/create-dmg/create-dmg/issues/109 476 | # 477 | # Based on a thread from dawn2dusk & peterguy 478 | # https://developer.apple.com/forums/thread/668084 479 | # 480 | EULA_RESOURCES_FILE=$(mktemp -t createdmg.tmp.XXXXXXXXXX) 481 | EULA_FORMAT=$(file -b ${EULA_RSRC}) 482 | if [[ ${EULA_FORMAT} == 'Rich Text Format data'* ]] ; then 483 | EULA_FORMAT='RTF ' 484 | else 485 | EULA_FORMAT='TEXT' 486 | fi 487 | # Encode the EULA to base64 488 | # Replace 'openssl base64' with 'base64' if Mac OS X 10.6 support is no more needed 489 | # EULA_DATA="$(base64 -b 52 "${EULA_RSRC}" | sed s$'/^\(.*\)$/\t\t\t\\1/')" 490 | EULA_DATA="$(openssl base64 -in "${EULA_RSRC}" | tr -d '\n' | awk '{gsub(/.{52}/,"&\n")}1' | sed s$'/^\(.*\)$/\t\t\t\\1/')" 491 | # Fill the template with the custom EULA contents 492 | eval "cat > \"${EULA_RESOURCES_FILE}\" </dev/null 2>/dev/null; then 511 | hdiutil internet-enable -yes "${DMG_DIR}/${DMG_NAME}" 512 | else 513 | echo "hdiutil does not support internet-enable. Note it was removed in macOS 10.15." 514 | fi 515 | fi 516 | 517 | if [[ -n "${SIGNATURE}" && "${SIGNATURE}" != "-null-" ]]; then 518 | echo "Codesign started" 519 | codesign -s "${SIGNATURE}" "${DMG_DIR}/${DMG_NAME}" 520 | dmgsignaturecheck="$(codesign --verify --deep --verbose=2 --strict "${DMG_DIR}/${DMG_NAME}" 2>&1 >/dev/null)" 521 | if [ $? -eq 0 ]; then 522 | echo "The disk image is now codesigned" 523 | else 524 | echo "The signature seems invalid${NC}" 525 | exit 1 526 | fi 527 | fi 528 | 529 | if [[ -n "${NOTARIZE}" && "${NOTARIZE}" != "-null-" ]]; then 530 | echo "Notarization started" 531 | xcrun notarytool submit "${DMG_DIR}/${DMG_NAME}" --keychain-profile "${NOTARIZE}" --wait 532 | echo "Stapling the notarization ticket" 533 | staple="$(xcrun stapler staple "${DMG_DIR}/${DMG_NAME}")" 534 | if [ $? -eq 0 ]; then 535 | echo "The disk image is now notarized" 536 | else 537 | echo "$staple" 538 | echo "The notarization failed with error $?" 539 | exit 1 540 | fi 541 | fi 542 | 543 | # All done! 544 | echo "Disk image done" 545 | exit 0 546 | -------------------------------------------------------------------------------- /doc-project/Developer Notes.md: -------------------------------------------------------------------------------- 1 | # create-dmg Developer Notes 2 | 3 | ## Repo layout 4 | 5 | - `create-dmg` in the root of the repo is the main program 6 | - `support/` contains auxiliary scripts used by `create-dmg`; it must be at that relative position to `create-dmg` 7 | - `builder/` contains ???? 8 | - `examples/` contains user-facing examples 9 | - `tests/` contains regression tests for developers 10 | - `doc-project/` contains developer-facing documentation about this project 11 | 12 | ### tests/ 13 | 14 | The `tests/` folder contains regression tests for developers. 15 | 16 | Each test is in its own subfolder. 17 | Each subfolder name should start with a 3-digit number that is the number of the corresponding bug report in create-dmg's GitHub issue tracker. 18 | 19 | The tests are to be run manually, with the results examined manually. 20 | There's no automated script to run them as a suite and check their results. 21 | That might be nice to have. 22 | 23 | ### examples/ 24 | 25 | Each example is in its own subfolder. 26 | The subfolder prefix number is arbitrary; these numbers should roughly be in order of "advancedness" of examples, so it makes sense for users to go through them in order. 27 | 28 | ## Versioning 29 | 30 | As of May 2020, we're using SemVer versioning. 31 | The old version numbers were 4-parters, like "1.0.0.7". 32 | Now we use 3-part SemVer versions, like "1.0.8". 33 | This change happened after version 1.0.0.7; 1.0.8 is the next release after 1.0.0.7. 34 | 35 | The suffix "-SNAPSHOT" is used to denote a version that is still under development. 36 | -------------------------------------------------------------------------------- /doc-project/Release Checklist.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | - Update the version in `create-dmg`'s `pure_version` function 4 | - Remove the "-SNAPSHOT" suffix 5 | - Commit 6 | - Tag the release as `vX.X.X` 7 | - `git push --tags` 8 | - Create a release on the GitHub project page 9 | - Open development on the next release 10 | - Bump the version number and add a "-SNAPSHOT" suffix to it 11 | -------------------------------------------------------------------------------- /examples/01-main-example/installer_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flipperdevices/create-dmg/11cff56757861851cdbec5c20c9493fc6f87a32f/examples/01-main-example/installer_background.png -------------------------------------------------------------------------------- /examples/01-main-example/sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -e ../../create-dmg ]]; then 4 | # We're running from the repo 5 | CREATE_DMG=../../create-dmg 6 | else 7 | # We're running from an installation under a prefix 8 | CREATE_DMG=../../../../bin/create-dmg 9 | fi 10 | 11 | # Since create-dmg does not clobber, be sure to delete previous DMG 12 | [[ -f Application-Installer.dmg ]] && rm Application-Installer.dmg 13 | 14 | # Create the DMG 15 | $CREATE_DMG \ 16 | --volname "Application Installer" \ 17 | --background "installer_background.png" \ 18 | --window-pos 200 120 \ 19 | --window-size 800 400 \ 20 | --icon-size 100 \ 21 | --icon "Application.app" 200 190 \ 22 | --hide-extension "Application.app" \ 23 | --app-drop-link 600 185 \ 24 | "Application-Installer.dmg" \ 25 | "source_folder/" 26 | -------------------------------------------------------------------------------- /examples/01-main-example/source_folder/Application.app: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flipperdevices/create-dmg/11cff56757861851cdbec5c20c9493fc6f87a32f/examples/01-main-example/source_folder/Application.app -------------------------------------------------------------------------------- /support/eula-resources-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LPic 6 | 7 | 8 | Attributes 9 | 0x0000 10 | Data 11 | 12 | AAAAAgAAAAAAAAAAAAQAAA== 13 | 14 | ID 15 | 5000 16 | Name 17 | 18 | 19 | 20 | STR# 21 | 22 | 23 | Attributes 24 | 0x0000 25 | Data 26 | 27 | AAYNRW5nbGlzaCB0ZXN0MQVBZ3JlZQhEaXNhZ3JlZQVQcmludAdT 28 | YXZlLi4ueklmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0 29 | aGlzIGxpY2Vuc2UsIGNsaWNrICJBZ3JlZSIgdG8gYWNjZXNzIHRo 30 | ZSBzb2Z0d2FyZS4gIElmIHlvdSBkbyBub3QgYWdyZWUsIHByZXNz 31 | ICJEaXNhZ3JlZS4i 32 | 33 | ID 34 | 5000 35 | Name 36 | English buttons 37 | 38 | 39 | Attributes 40 | 0x0000 41 | Data 42 | 43 | AAYHRW5nbGlzaAVBZ3JlZQhEaXNhZ3JlZQVQcmludAdTYXZlLi4u 44 | e0lmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0aGlzIGxp 45 | Y2Vuc2UsIHByZXNzICJBZ3JlZSIgdG8gaW5zdGFsbCB0aGUgc29m 46 | dHdhcmUuICBJZiB5b3UgZG8gbm90IGFncmVlLCBwcmVzcyAiRGlz 47 | YWdyZWUiLg== 48 | 49 | ID 50 | 5002 51 | Name 52 | English 53 | 54 | 55 | ${EULA_FORMAT} 56 | 57 | 58 | Attributes 59 | 0x0000 60 | Data 61 | 62 | ${EULA_DATA} 63 | 64 | ID 65 | 5000 66 | Name 67 | English 68 | 69 | 70 | TMPL 71 | 72 | 73 | Attributes 74 | 0x0000 75 | Data 76 | 77 | E0RlZmF1bHQgTGFuZ3VhZ2UgSUREV1JEBUNvdW50T0NOVAQqKioq 78 | TFNUQwtzeXMgbGFuZyBJRERXUkQebG9jYWwgcmVzIElEIChvZmZz 79 | ZXQgZnJvbSA1MDAwRFdSRBAyLWJ5dGUgbGFuZ3VhZ2U/RFdSRAQq 80 | KioqTFNURQ== 81 | 82 | ID 83 | 128 84 | Name 85 | LPic 86 | 87 | 88 | styl 89 | 90 | 91 | Attributes 92 | 0x0000 93 | Data 94 | 95 | AAMAAAAAAAwACQAUAAAAAAAAAAAAAAAAACcADAAJABQBAAAAAAAA 96 | AAAAAAAAKgAMAAkAFAAAAAAAAAAAAAA= 97 | 98 | ID 99 | 5000 100 | Name 101 | English 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /support/template.applescript: -------------------------------------------------------------------------------- 1 | on run (volumeName) 2 | tell application "Finder" 3 | tell disk (volumeName as string) 4 | open 5 | 6 | set theXOrigin to WINX 7 | set theYOrigin to WINY 8 | set theWidth to WINW 9 | set theHeight to WINH 10 | 11 | set theBottomRightX to (theXOrigin + theWidth) 12 | set theBottomRightY to (theYOrigin + theHeight) 13 | set dsStore to "\"" & "/Volumes/" & volumeName & "/" & ".DS_STORE\"" 14 | 15 | tell container window 16 | set current view to icon view 17 | set toolbar visible to false 18 | set statusbar visible to false 19 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY} 20 | set statusbar visible to false 21 | REPOSITION_HIDDEN_FILES_CLAUSE 22 | end tell 23 | 24 | set opts to the icon view options of container window 25 | tell opts 26 | set icon size to ICON_SIZE 27 | set text size to TEXT_SIZE 28 | set arrangement to not arranged 29 | end tell 30 | BACKGROUND_CLAUSE 31 | 32 | -- Positioning 33 | POSITION_CLAUSE 34 | 35 | -- Hiding 36 | HIDING_CLAUSE 37 | 38 | -- Application and QL Link Clauses 39 | APPLICATION_CLAUSE 40 | QL_CLAUSE 41 | close 42 | open 43 | -- Force saving of the size 44 | delay 1 45 | 46 | tell container window 47 | set statusbar visible to false 48 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10} 49 | end tell 50 | end tell 51 | 52 | delay 1 53 | 54 | tell disk (volumeName as string) 55 | tell container window 56 | set statusbar visible to false 57 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY} 58 | end tell 59 | end tell 60 | 61 | --give the finder some time to write the .DS_Store file 62 | delay 3 63 | 64 | set waitTime to 0 65 | set ejectMe to false 66 | repeat while ejectMe is false 67 | delay 1 68 | set waitTime to waitTime + 1 69 | 70 | if (do shell script "[ -f " & dsStore & " ]; echo $?") = "0" then set ejectMe to true 71 | end repeat 72 | log "waited " & waitTime & " seconds for .DS_STORE to be created." 73 | end tell 74 | end run 75 | -------------------------------------------------------------------------------- /tests/007-space-in-dir-name/my files/hello.txt: -------------------------------------------------------------------------------- 1 | Hello world 2 | -------------------------------------------------------------------------------- /tests/007-space-in-dir-name/run-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Test for https://github.com/create-dmg/create-dmg/issues/7 - spaces in folder names 4 | 5 | ../../create-dmg "my disk image.dmg" "my files" --------------------------------------------------------------------------------