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

mk_dmg(name, root, out_dir = DEFAULT_OUT_DIR) click to toggle source

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
mk_pkg(name, version, root, **opts) click to toggle source

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