Skip to main content

MacSoftware

Use MacSoftware for an existing installer or application. Stemma selects the application, inspects its metadata and prepares content suitable for publication. Use BuildMacPkg when you need to construct a custom payload.

Start with the vendor's installer​

SourceWhat Stemma prepares
PKGThe original vendor package
DMG containing an applicationThe original DMG, with the selected application described for the destination
DMG or archive containing an installer PKGThe selected nested package, preserving its bytes
Archive or tree containing an applicationA new DMG holding the selected application
Any of those applications, with packageA new PKG that installs the selected application
Archive, tree or DMG containing a DMGWhat the inner DMG prepares as a download of its own

For an application in a DMG:

apiVersion: stemma/v1alpha1
kind: MacSoftware
metadata:
name: example-app
spec:
source:
path: Assets/Example.dmg
application:
path: Example.app
destinations:
munki:
pkginfo:
description: Example application.

This assumes an Assets/Example.dmg beside the document and a Project connection named munki. Substitute your application's actual path.

Publish an application from an archive​

By default, a vendor's PKG or DMG keeps its original bytes. An application that arrives on its own, in a ZIP or TAR archive or as a committed .app tree, is placed at the root of a new DMG. One document covers a GitHub release:

apiVersion: stemma/v1alpha1
kind: MacSoftware
metadata:
name: example-app
spec:
source:
resolver: github
repository: company/application
asset: Example-*.zip
signatures:
- signer: apple:developer-id:ABCDE12345
destinations:
munki:
pkginfo:
description: Example application.

The image holds the application: its files, permission bits, symlinks and archive extended attributes, including resource forks. AppleDouble sidecars and PAX attributes stay associated with their files without being applied to the runner. Ownership and dates are normalized, so the same source prepares the same bytes on any runner. Munki copies the application with copy_from_dmg and Intune publishes the image as type: dmg. The application installs to /Applications/<name>.app unless application.installed_path says otherwise. Signature verifies the application; the image is a container and carries no signature of its own.

Compress the image​

disk_image.compression controls how a disk image created by Stemma is compressed:

spec:
disk_image:
compression: lzma
compressionImage sizeCopy time on a MacCompatibility
lzfse (default)5–6% smaller11–17% fastermacOS 10.11 or later
zlibbaselinebaselineany macOS
lzma12–18% smaller2.4–4.5x slowermacOS 10.15 or later

lzfse is the default because it produces a slightly smaller image than zlib while also being faster for Macs to read.

lzma produces the smallest images, but takes 3.5–5x as long to build and is slower to copy files from. It is most useful when image size matters more than preparation and installation time.

The figures above come from Chrome, Visual Studio Code and Minecraft Education. Image creation happens once per release, while managed Macs pay the read cost each time the image is mounted and its application is copied.

For reference, hdiutil calls the LZFSE, zlib and LZMA image formats ULFO, UDZO and ULMO respectively. LZMA also requires roughly four times as much memory while building.

disk_image applies only when Stemma creates the image. Vendor DMGs and PKGs keep their original bytes, so declaring disk_image for one causes preparation to fail.

Publish an application in a package​

package publishes the selected application in a new component PKG. It applies wherever the application comes from: an archive, a tree or a vendor DMG.

spec:
source:
resolver: github
repository: company/application
asset: Example-*.zip
package: {}
signatures:
- signer: apple:developer-id:ABCDE12345

The package installs the application at application.installed_path, /Applications/<name>.app by default, owned by root. Its receipt takes the application's version, so application.version_key selects the receipt version too. Dates are normalized, and the same application prepares the same package from any source on any runner.

FieldDefaultPurpose
identifierThe application's bundle identifierIdentifies the installation receipt
compressiongzipPayload compression

Choose a package when a destination needs one. Jamf publishes only PKGs, and Intune runs install scripts only for PKG apps. Munki installs it as a package and detects both its receipt and the application. A disk image holds the application and nothing else; a package also leaves a receipt on each Mac.

Signature verifies the application. The package is a container and carries no signature of its own, so it can't publish as an Intune line-of-business app. A package payload holds no extended attributes: an application that keeps code signatures in them fails to package. Omit package to publish it in a disk image.

The package has no scripts and no other payload; BuildMacPkg builds those. A vendor PKG keeps its original bytes, so package fails for one, and it doesn't combine with package_path or disk_image.

Select an application once​

Inspection lists every application and package in the installer. Disk images and archives containing one application select it automatically. Packages default to installer metadata and receipt detection. To use an application inside a package, or select among several applications, set application.path or application.bundle_id:

application:
bundle_id: com.google.Chrome

The selection supplies coherent version, bundle and supported icon evidence to destinations. A helper application should not accidentally become the version or detection source for the main application. The other applications stay in the published facts.

Archive paths and endpoint paths are different:

application:
path: Release/Example.app
installed_path: /Applications/Example.app
version_key: CFBundleVersion

path locates the application inside the input. installed_path describes its location on a managed Mac; it is not a path Stemma reads on the runner. The default version key is CFBundleShortVersionString, unless that value is missing or does not begin with a digit and CFBundleVersion is present. Select CFBundleVersion when the publisher's build number is the version you need to manage.

For an existing PKG, Stemma does not rewrite its payload to match a declared endpoint path. Keep that path consistent with the vendor installer.

Detect several applications​

application selects the one application whose version the software takes. To detect a suite by several applications, name them under subjects and list them as a Munki destination's installs.

spec:
subjects:
draw:
bundle_id: com.vendor.suite.draw
graph:
bundle_id: com.vendor.suite.graph
destinations:
munki:
pkginfo:
version: "{{ facts.draw.app.version }}"
installs:
- "{{ facts.draw }}"
- "{{ facts.graph }}"

Each listed application becomes an installs item at its installed path, with its bundle identifier and version. Detection and the managed version are separate decisions: installs says which Macs need the installer, and version names the release. Stemma takes that version from the installer, the selected application or the declaration, and never composes one.

A release whose version does not change still reaches the destination: its content changes the lock, and publication replaces the item published under that version. Munki then installs it on every Mac where an installs item is older. Changed bytes alone do not trigger installation when the detection versions remain unchanged. State a version that changes with each release when the earlier one should stay published beside it.

Select a nested installer​

A driver download such as Wacom can contain a PKG inside a DMG. When the image or archive holds more than one application or package, select the installer by its archive-relative path:

spec:
source:
path: Assets/WacomDriver.dmg
package_path: Install Wacom Tablet.pkg
destinations:
munki:
pkginfo:
description: Wacom tablet driver.

Use the name present in your download. package_path also accepts a glob, but it must identify exactly one package. This selects and extracts the vendor installer; it does not reconstruct its payload or run its scripts. application then selects within the extracted package, as it does when an image holds a single package and no application matches.

A download that wraps one disk image, such as a ZIP holding a DMG, needs no extra selection: package_path and application apply inside that image. When a download holds several images, package_path names the one to open and application selects inside it.

Some packages install a staging helper which later downloads the real application. Their contents cannot prove the eventual installed application. Leave application unset and set the destination's detection fields explicitly.

Minimum macOS​

Destinations receive one minimum macOS: minimum_os when it is set, otherwise the latest of the installer's requirement and the selected application's LSMinimumSystemVersion:

spec:
minimum_os: "14.0"

Changing minimum_os reuses prepared installers. Each destination maps the value to its own field, as described in publishing.

Publish without an installer​

Munki nopkg items need no fake source or package:

apiVersion: stemma/v1alpha1
kind: MacSoftware
metadata:
name: example-setting
spec:
destinations:
munki:
pkginfo:
installer_type: nopkg
version: "1.0"
description: Set an example machine preference.
installcheck_script: |
#!/bin/sh
value=$(/usr/bin/defaults read /Library/Preferences/org.example.settings Enabled 2>/dev/null)
[ "$value" = "1" ] && exit 1
exit 0
postinstall_script: |
#!/bin/sh
/usr/bin/defaults write /Library/Preferences/org.example.settings Enabled -bool true

Munki's install check exits zero when installation is needed. These scripts run on the endpoint through Munki, never during preparation. Other destinations must explicitly support a source-free deployment mode; accepting PKG files alone does not imply that support.

Signature​

Declare one exact signing expectation for each published signing subject:

signatures:
- signer: apple:developer-id:UBF8T346G9 # Microsoft Corporation

An entry without subject covers the artifact's only signing subject. For a PKG that is the outer package signature: component receipts and payload applications do not prove that the published PKG is signed. For a DMG it is the application the image holds. An image holding several top-level applications needs an entry for each, selected by its exact path or bundle identifier:

signatures:
- subject:
path: Example.app
signer: apple:developer-id:ABCDE12345
- subject:
path: Example Helper.app
signer: apple:developer-id:ABCDE12345

Every shipped top-level app requires an entry when signatures is present, including companion apps. Nested code is covered by its enclosing app's signature. An app selected from an archive is published alone at the new image's root, so its entry needs no subject. Neither does the application package publishes, which is that package's only signing subject.

stemma signature MacSoftware/<name> inspects every signing subject and prints a complete fragment, independently of existing expectations. It needs no placeholder declaration. Different apps may name different publishers. The comment is display information only.

A signer names the publisher's Apple team under the scheme of the certificate that signed the software. apple:developer-id:<TEAMID> is a PKG or application the publisher signed with its own Developer ID. An application from the Mac App Store is signed by Apple instead, and its signer is apple:app-store:<TEAMID>:

signatures:
- signer: apple:app-store:ABCDE12345

Apple's certificate names no publisher, so the team is the one the signed application names. The schemes are separate expectations: an application that moves between Developer ID and the App Store changes signer.

Unsigned software can be asserted explicitly:

signatures:
- unsigned: true

Each entry requires exactly one of signer or unsigned: true. Omitting signatures makes no assertion. An unsigned expectation fails when the subject becomes signed; a signer expectation fails when it becomes unsigned or changes publisher. Malformed, tampered, ad-hoc and unsupported signatures remain errors in both preparation and derivation.

A package from BuildMacPkg needs no entry. The builder never signs, so the package has no publisher to verify and stemma signature reports nothing for it. unsigned: true is still checked when declared. A package that package builds is unsigned in the same way, and its entry is the application's.

Apple verification covers every architecture's code, Info.plist, the resource envelope, symlinks and nested code, chained to Apple's roots. A trusted timestamp fixes the time at which every certificate of a signature must have been valid. Without one, a PKG's certificates are judged when it is verified, and an application's are held to no validity period, as on macOS: App Store signatures carry no timestamp, and Apple's certificate for them expires while the applications stay installed. A Developer ID application without a trusted timestamp verifies with a warning: nothing shows that its certificate was valid when it signed. Code signed to expire with its certificate needs a trusted timestamp. Nested code matches its exact recorded cdhash, or replaces the sealed code under the Developer ID requirement the app recorded for it: the same identifier, signed by the same team with a Developer ID Application certificate. stemma signature lists replaced nested code. A framework's non-current versions must satisfy the same requirement. Nested scripts and data files that codesign signs in extended attributes verify inside vendor DMGs and archives that carry those attributes. Generated images preserve them; vendor DMGs are published unchanged unless package selects PKG preparation. Notarisation and Gatekeeper policy are not assessed. See signature limits.

Icons​

An icon is a committed catalog asset, not something a run derives. Declare it by name:

spec:
icon: microsoft-word

icon: microsoft-word means icons/microsoft-word.png at the project root. Every destination publishes those exact bytes on any runner, and a changed file is ordinary drift that the next run replaces. Without icon, published artwork is left unchanged. A declared icon without its file fails validate, plan and apply.

Create the asset from the software itself:

stemma icon MacSoftware/microsoft-word
stemma icon

stemma icon prepares the locked source like any other run and takes the application that selection identifies. Disk images, archives and vendor packages all work; a package needs application.bundle_id or application.path first. Without selectors it creates the declared icons that have no file yet and existing files stay unchanged, so a run across the catalog is safe. --force replaces them.

Both presentations select the file named by CFBundleIconFile, adding .icns when the declaration omits an extension.

glassy, the default on a Mac, uses the macOS renderer at 512 pixels; --size changes the edge. It stages the declared icon, Info.plist, PkgInfo when present, and Assets.car when CFBundleIconName declares an asset-backed icon. The renderer selects the artwork from those resources. A small native Mach-O marker prevents missing-executable badges; vendor executables and their symlink targets are never extracted or run. Applications with only a named asset icon do not require an ICNS fallback. Missing artwork reports no artwork.

Output size can change the macOS presentation, not just its resolution. For some applications, 256 pixels produces the expected app icon while 512 pixels adds a grey frame around the artwork. If the result looks wrong, compare sizes with stemma icon MacSoftware/<name> --force --size 256. Review the generated image; a larger size does not necessarily produce a better match to Finder.

raw, the default elsewhere, decodes the declared ICNS or raster icon and writes its largest artwork as PNG. ICNS supports PNG, JPEG 2000, planar colour and indexed elements. Small artwork is enlarged to 128 pixels; larger or rectangular artwork is fitted to the asset bounds. Valid PNG assets retain their bytes. Unreadable artwork reports a decoding error; an asset-catalog-only icon requires glassy. --presentation selects either presentation explicitly. A package's payload is still read through once to reach the selected files, which requires decompressing it.

For a script-only wrapper, declare icon: example on the software resource and run stemma icon MacSoftware/example --input vendor --path Installer.app. The command finds vendor through the build dependencies and writes icons/example.png. An input containing multiple applications requires --path.

A vendor package that holds several applications and selects none reports them. Name one with --path alone, such as stemma icon MacSoftware/suite --path "Payload/Example.app"; the package is read where it is prepared, however deeply the source nests it.

Software without artwork of its own, such as fonts or a configuration package, can take another application's icon from the machine:

stemma icon MacSoftware/fonts --from "/System/Applications/Font Book.app"

--from reads an application, an ICNS or image file, or a folder, archive, disk image or package that holds applications, and --path selects one of several. In a package, --path is the application's path as stemma inspect reports it, such as Payload/Example.app. The resource needs only its icon declaration: it can have no source, and nothing is locked or prepared.

icons/ holds plain PNG files: square, between 128 and 1024 pixels, up to 1 MiB. You can also commit an asset directly. Resources share an asset by naming it, so a WindowsSoftware document can publish the icon created from its macOS counterpart, or create its own.