module Jamf::Composer
This module provides two methods for building very simple Casper-happy .pkg and .dmg packages for deployment.
Unlike Composer.app from JAMF, this module currently doesn’t offer a way to do a before/after disk scan and use the differences to build the root folder from which the package is built. Nor does the module support editing the pre/post install scripts in .pkgs.
The ‘root folder’, a folder representing the root filesystem of the target machine where the package will be installed, must already exist and be fully populated and with correct permissions.
Constants
- DEFAULT_OUT_DIR
Where to save the output ?
- HDI_UTIL
Apple’s hdiutil for making dmgs
- PKGBUILD
The location of the cli tool for making .pkgs
- PKG_BUNDLE_ID_PFX
the default bundle identifier prefix for pkgs
- PKG_UTIL
the apple pkgutil tool
Public Class Methods
Make a casper-happy .dmg out of a root folder, permissions are assumed to be correct.
@param name The name of the .dmg, the suffix will be added if needed
@param root[String, Pathname] the path to the “root folder” representing the root file system of the target install drive
@param out_dir[String, Pathname] the folder in which the .pkg will be created. Defaults to {DEFAULT_OUT_DIR}
@return [Pathname] the local path to the new .dmg
# File lib/jamf/composer.rb 172 def self.mk_dmg(name, root, out_dir = DEFAULT_OUT_DIR) 173 dmg_filename = "#{name}.dmg" 174 dmg_vol = name 175 dmg_out = Pathname.new "#{out_dir}/#{dmg_filename}" 176 if dmg_out.exist? 177 mv_to = dmg_out.dirname + "#{dmg_out.basename}.#{Time.now.strftime('%Y%m%d%H%M%S')}" 178 dmg_out.rename mv_to 179 end # if dmg out exist 180 181 ### TODO - this may need to be sudo'd to handle proper internal permissions. 182 system "#{HDI_UTIL} create -volname '#{dmg_vol}' -scrub -srcfolder '#{root}' '#{dmg_out}'" 183 184 raise 'There was an error building the .dmg' unless $?.exitstatus.zero? 185 Pathname.new dmg_out 186 end
Make a casper-happy .pkg out of a root folder, permissions are assumed to be correct.
@param name the name of the .pkg. The .pkg suffix will be added if not present
@param version the version of the .pkg, needed for building the .pkg
@param root[String, Pathname] the path to the ‘root folder’ representing
the root file system of the target install drive
@param opts the options for building the .pkg
@options opts :pkg_id the full package if for the new pkg.
e.g. 'com.mycompany.myapp'
@option opts :bundle_id_prefix the pkg bundle identifier prefix.
If no :pkg_id is provided, one is made using this prefix and the name provided. e.g. 'com.mycompany' Defaults to '{PKG_BUNDLE_ID_PFX}'. See 'man pkgbuild' for more info
@option opts :out_dir he folder in which the .pkg will be
created. Defaults to {DEFAULT_OUT_DIR}
@option opts :preserve_ownership If true, the owner/group of the
rootpath are preserved. Default is false: they become the pkgbuild/installer 'recommended' (root/wheel or root/admin)
@option opts :signing_identity the optional name of the signing identity (certificate) to
use for signing the pkg. See `man pkgbuild` for details
@option opts :signing_options the optional string of options to pass to pkgbuild.
See `man pkgbuild` for details
@return [Pathname] the local path to the new .pkg
# File lib/jamf/composer.rb 95 def self.mk_pkg(name, version, root, **opts) 96 raise NoSuchItemError, "Missing pkgbuild tool. Please make sure you're running 10.8 or later." unless PKGBUILD.executable? 97 98 opts[:out_dir] ||= DEFAULT_OUT_DIR 99 opts[:bundle_id_prefix] ||= PKG_BUNDLE_ID_PFX 100 101 pkg_filename = name.end_with?('.pkg') ? name : name + '.pkg' 102 pkg_id = opts[:pkg_id] 103 pkg_id ||= opts[:bundle_id_prefix] + '.' + name 104 pkg_out = "#{opts[:out_dir]}/#{pkg_filename}" 105 pkg_ownership = opts[:preserve_ownership] ? 'preserve' : 'recommended' 106 107 if opts[:signing_identity] 108 signing = "--sign '#{opts[:signing_identity]}'" 109 signing << " #{opts[:signing_options]}" if opts[:signing_options] 110 else 111 signing = '' 112 end # if opts[:signing_identity] 113 114 ### first, run 'analyze' to get a 'component plist' in which we can change some settings 115 ### for any bundles in the root (bundles like .apps, frameworks, plugins, etc..) 116 ### 117 ### we edit the settings thus: 118 ### BundleOverwriteAction = upgrade, totally replace any version current on disk 119 ### BundleIsVersionChecked = false, allow us to install regardless of what version is currently installed 120 ### BundleIsRelocatable = false, if there's a version of this in some other location, Do Not move this one there after installation 121 ### BundleHasStrictIdentifier = false, don't care if there's something at the install path with a different bundle id. 122 ### 123 ### In other words, just install the thing! 124 ### (see 'man pkgbuild' for more info) 125 ### 126 ### 127 comp_plist_out = Pathname.new "/tmp/#{PKG_BUNDLE_ID_PFX}-#{pkg_filename}.plist" 128 system "#{PKGBUILD} --analyze --root '#{root}' '#{comp_plist_out}'" 129 comp_plist = JSS.parse_plist comp_plist_out 130 131 ### if the plist is empty, there are no bundles in the pkg 132 if comp_plist[0].nil? 133 comp_plist_arg = '' 134 else 135 ### otherwise, edit the bundle dictionaries 136 comp_plist.each do |bndl| 137 bndl.delete 'ChildBundles' if bndl['ChildBundles'] 138 bndl['BundleOverwriteAction'] = 'upgrade' 139 bndl['BundleIsVersionChecked'] = false 140 bndl['BundleIsRelocatable'] = false 141 bndl['BundleHasStrictIdentifier'] = false 142 end 143 ### write out the edits 144 comp_plist_out.open('w') { |f| f.write JSS.xml_plist_from(comp_plist) } 145 comp_plist_arg = "--component-plist '#{comp_plist_out}'" 146 end 147 148 ### now build the pkg 149 begin 150 it_built = system "#{PKGBUILD} --identifier '#{pkg_id}' --version '#{version}' --ownership #{pkg_ownership} --install-location / --root '#{root}' #{signing} #{comp_plist_arg} '#{pkg_out}'" 151 152 raise 'There was an error building the .pkg' unless it_built 153 ensure 154 comp_plist_out.delete if comp_plist_out.exist? 155 end 156 157 Pathname.new pkg_out 158 end