smithjw's AutoPkg recipes
autopkg repo-add smithjw-recipesRecipes require AutoPkg 2.9 or higher. The repo standardises on
URLDownloaderPython and the bundled StopProcessingIf download_changed
predicate for cache-aware downloads; both became dependable defaults in 2.9.
Most apps in this repo follow a four-step chain so that overrides can plug in at the level of granularity they need:
download -> pkg -> upload (Jamf-only) -> jamf (full Self Service flow)
-
<App>.download.recipe.yaml— fetches the upstream artefact viaURLDownloaderPython, then verifies the signature and short-circuits the rest of the chain when nothing has changed:- Processor: URLDownloaderPython Arguments: download_missing_file: '%DOWNLOAD_MISSING_FILE%' filename: '<App>.dmg' url: '%DOWNLOAD_URL%' - Processor: EndOfCheckPhase - Processor: StopProcessingIf Arguments: predicate: 'download_changed == %BYPASS_STOP_PROCESSING_IF_DOWNLOAD_UNCHANGED%'
The pair of input keys
DOWNLOAD_MISSING_FILEandBYPASS_STOP_PROCESSING_IF_DOWNLOAD_UNCHANGEDexist on every download recipe and have safe defaults (null/'False').URLDownloaderPythoncaches downloads by HTTPETag/Last-Modified/Content-Lengthso unchanged downloads are no-ops, and the predicate makes sure the pkg / upload steps are skipped too. SetBYPASS_STOP_PROCESSING_IF_DOWNLOAD_UNCHANGED=Truein an override when you need to force a rebuild. -
<App>.pkg.recipe.yaml— wraps the downloaded artefact into a.pkgnamed<SOFTWARE_TITLE>-<version>.pkg. Universal recipes (where the filename includes-Universal-) wrap separate arm64 and x86_64 component pkgs into one installer whose postinstall script picks the right component at install time. -
<App>.upload.jamf.recipe.yaml— uploads the pkg to Jamf Pro using grahampugh/jamf-upload and prunes older copies (keeping the two most recent matches by default). It stops at "upload + cleanup" so that downstream override repos can layer their own policy / scope on top without inheriting any policy from this repo. -
<App>.jamf.recipe.yaml— the legacy full-flow recipe that also creates a Self Service policy and smart group from the templates at the repo root. Newer recipes prefer the.upload.flavour and let downstream overrides own the policy. Both still ship for backwards compatibility.
Several recipes additionally provide .sign.recipe.yaml,
.install.recipe.yaml, .munki.recipe.yaml, or -Patch.jamf.recipe.yaml
companions that parent off the pkg recipe identifier.
The stock URLDownloader re-downloads files on every run, then relies on the
processor that wraps it (typically a packaging or upload step) to decide
whether to keep working. That's wasteful in CI and makes "did anything
actually change?" hard to answer when scanning logs.
URLDownloaderPython lifts ETag/Last-Modified/Content-Length comparison into
the download itself and emits a download_changed boolean that the
StopProcessingIf predicate consumes. The pkg / upload steps simply never run
when there's nothing new. Side-effects (pkg signing, Jamf uploads, package
cleanup) are skipped automatically without per-processor pkg_uploaded checks
scattered across the chain.
The processor ships in core AutoPkg from 2.9 onwards, so recipes pin
MinimumVersion: '2.9' and no extra parent repo is required to use it.
smithjw-recipes/
├── <App>/ # Per-app folders
│ ├── <App>.download.recipe.yaml
│ ├── <App>.pkg.recipe.yaml
│ ├── <App>.upload.jamf.recipe.yaml # Upload-only; preferred for overrides
│ ├── <App>.jamf.recipe.yaml # Legacy full Self Service flow
│ ├── <App>.png # Self Service icon
│ └── <App>.<sign|install|munki>.recipe.yaml # Optional
├── SharedProcessors/ # Custom processors (FriendlyPathDeleter)
├── _Jamf Recipes/ # Auxiliary Jamf templates
├── Policy-*.xml / SmartGroup-*.xml # Template XML used by .jamf.recipe.yaml
└── PatchTemplate-*.xml # Templates used by -Patch.jamf.recipe.yaml