Skip to main content
The Ops Playbook

Retiring Manual App Repackaging With AutoPkg Patch Pipelines

Stop stale-installer support tickets by automating macOS third-party app packaging and patch compliance with AutoPkg, JamfUploader and Jamf Pro.

Retiring Manual App Repackaging With AutoPkg Patch Pipelines
Isla MorganIsla Morgan13 min readTier L245 min

This playbook covers

Share

Every managed Mac fleet carries the same quiet tax: somebody has to keep Chrome, Zoom, Slack and a dozen other third-party titles current, correctly signed, and matched to whatever Jamf Pro’s Self Service catalogue promises. The old way looks like this: a vulnerability scanner or a help desk ticket flags an outdated version, an L2 technician downloads the vendor installer by hand, rebuilds a signed PKG in Composer, tests it on a spare Mac, uploads it to Jamf Pro, edits the patch policy, waits for smart group scoping to catch up, and closes the ticket. Then it happens again next month, for the same title, and again for the next fifteen titles on the list. In between cycles the package sits stale, and stale packages are exactly what causes silent install failures once Apple tightens Gatekeeper requirements or a vendor bumps its minimum supported OS without warning.

The new way removes the human from the repetitive part of that loop entirely. A dedicated build host runs AutoPkg with JamfUploader every night, pulls the latest vendor installer, verifies its code signature against Apple’s chain of trust, uploads the package to Jamf Pro, updates the Patch Software Title definition, and deploys to a pilot ring automatically. A human only gets involved when the automation detects something genuinely abnormal: a broken download URL, a signature mismatch, or a spike in pilot-ring failures. This playbook builds that pipeline, states exactly who is allowed to touch it, and defines the thresholds that should wake someone up.

#Why Stale Packages Generate High-Volume Tickets

Third-party vendors ship on their own schedule, not on your patch cadence. When Google, Zoom or Slack releases a new build, the old installer in Jamf Pro keeps working for a while, until Apple’s notarization and Gatekeeper checks reject an installer built against an SDK the current macOS release no longer trusts, or the vendor drops support for an OS version still in your fleet. Both failure modes produce the same ticket: “Self Service install failed” or “App won’t open after update”. Apple documents the notarization and code-signing checks that Gatekeeper performs at install and first-launch time, and those checks are exactly what an automated pipeline should verify before a package ever reaches a production Mac, not after a technician gets paged.

This pattern is not exclusive to Jamf Pro. Organisations managing Macs through Microsoft Intune can apply the same logic: replace the JamfUploader processors with a script step that calls the Microsoft Graph macOS line-of-business app endpoints to upload the new package and reassign it, gated behind the same pilot-ring and signature-verification checks. The automation logic below is transferable; only the upload target changes.

#Prerequisites and Permissions

  • A dedicated automation host running a currently supported macOS version, excluded from every deployment and patch policy in your MDM so it never receives the packages it builds.
  • AutoPkg installed on that host, with the JamfUploader processor repository added and trust information verified before first run.
  • A Jamf Pro API role scoped to the minimum required privileges: Create/Read/Update Packages, Create/Read/Update Patch Management Software Titles, Read Smart Computer Groups, and Create/Read/Update Policies. Do not grant Delete or Full Administrator rights to this role.
  • A Jamf Pro API client (client ID and secret) bound to that scoped role, generated under Settings, System, API Roles and Clients, not a personal admin account username and password.
  • A Slack or Microsoft Teams incoming webhook URL for automated run notifications.
  • A pilot smart group of 15 to 30 volunteer Macs, explicitly excluded from the production patch policy scope until promotion criteria are met.
  • A non-interactive service account on the build host with sudo limited to codesign and spctl verification only; the account should never be used for interactive login or general administration.
  • Secrets (API client secret, webhook URL) stored in a secrets manager or CI credential store, never committed to the recipe repository in plain text.

#Implementation Steps

Step 1: Provision and isolate the build host. Action: create a static computer group tagged Build-Host and exclude it from all deployment and patch policies in Jamf Pro. Expected result: the host appears in inventory but is never a target for any Self Service or patch policy. Evidence: exported policy exclusion criteria showing the Build-Host group listed under Exclusions for every deployment policy.

Step 2: Install AutoPkg and add trusted recipe repositories. Action: run the commands below on the build host.

1sudo installer -pkg autopkg-2.7.2.pkg -target /
2autopkg repo-add recipes
3autopkg repo-add grahampugh-recipes
4autopkg verify-trust-info com.github.grahampugh.recipes.jamf.GoogleChrome.jamf

Expected result: verify-trust-info exits 0 for every recipe you intend to run. Evidence: captured terminal output showing exit status 0; a non-zero exit halts the pipeline before any download occurs.

Step 3: Configure JamfUploader with scoped API client credentials. Action: write the client credentials into the AutoPkg preference domain rather than a shared admin login.

1defaults write com.github.autopkg JSS_URL "https://yourtenant.jamfcloud.com"
2defaults write com.github.autopkg CLIENT_ID "autopkg-pipeline-client"
3defaults write com.github.autopkg CLIENT_SECRET "REDACTED_SECRET_FROM_VAULT"

Expected result: JamfUploader authenticates using OAuth client credentials against the scoped API role only. Evidence: Jamf Pro API access log entry showing the client ID, not a named admin user, performing package and patch title updates.

AutoPkg Jamf Pro patch automation

Step 4: Build a version-controlled recipe list. Action: store an override recipe per title in a Git repository, scoping every upload to the pilot group first.

1Identifier: local.jamf.GoogleChrome
2ParentRecipe: com.github.grahampugh.jamf-upload.jamf.GoogleChrome
3Input:
4  NAME: Google Chrome
5  CATEGORY: Browsers
6  PKG_CATEGORY: Browsers
7  jamfpro_url: "https://yourtenant.jamfcloud.com"
8  PATCH_POLICY_NAME: "Google Chrome - Pilot Ring"
9  GROUP_NAME: "Patch-Pilot-Ring"
10  SELF_SERVICE_DESCRIPTION: "Latest Chrome, verified nightly."

Expected result: the recipe uploads only to the pilot patch policy scope, never directly to production. Evidence: diff-reviewed pull request against the recipe repository before merge, visible in Git history.

Step 5: Automate nightly execution with launchd. Action: install a launchd job that runs the recipe list at 02:00 local time and reports the result.

1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5  <key>Label</key>
6  <string>com.kby.autopkg.nightly</string>
7  <key>ProgramArguments</key>
8  <array>
9    <string>/usr/local/bin/autopkg</string>
10    <string>run</string>
11    <string>--recipe-list=/usr/local/autopkg-pipeline/recipe_list.txt</string>
12    <string>--report-plist=/usr/local/autopkg-receipts/run.plist</string>
13  </array>
14  <key>StartCalendarInterval</key>
15  <dict>
16    <key>Hour</key>
17    <integer>2</integer>
18    <key>Minute</key>
19    <integer>0</integer>
20  </dict>
21</dict>
22</plist>

Expected result: the job runs unattended every night and writes a report plist. Evidence: file timestamp on run.plist matching the scheduled time, and log entries in /var/log/autopkg.log.

Step 6: Gate production promotion behind pilot dwell and failure rate. Action: run a promotion check after 48 hours of pilot exposure that only widens scope if the pilot failure rate is low.

1import requests
2
3JSS_URL = "https://yourtenant.jamfcloud.com"
4TOKEN = "REDACTED_OAUTH_TOKEN"
5HEADERS = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"}
6
7def pilot_failure_rate(patch_title_id):
8    response = requests.get(
9        f"{JSS_URL}/api/v2/patch-software-title-configurations/{patch_title_id}/dashboard",
10        headers=HEADERS,
11    )
12    data = response.json()
13    failed = data.get("failedCount", 0)
14    total = data.get("totalCount", 1)
15    return failed / total
16
17threshold = 0.05
18rate = pilot_failure_rate("1024")
19
20if rate < threshold:
21    print("Pilot healthy, promoting to production scope")
22else:
23    print("Pilot failure rate too high, holding promotion")

Expected result: promotion only proceeds automatically when failure rate is under five percent. Evidence: promotion script log line and the corresponding Jamf Pro Policy History entry showing scope change timestamp.

Step 7: Fail closed on signature verification. Action: the CodeSignatureVerifier processor built into the recipe chain checks the downloaded installer against Apple’s notarization and signing requirements before JamfUploader is ever invoked. Expected result: a signature mismatch stops the run with a non-zero exit code and no upload occurs. Evidence: AutoPkg run log line reading CodeSignatureVerifier: The download did not match the expected signature, followed by a Slack failure alert rather than a silent skip.

Step 8: Retain run receipts for audit. Action: store the report plist from every run in /usr/local/autopkg-receipts for 90 days. Expected result: a queryable audit trail of every package version shipped, when, and by which recipe. Evidence: directory listing showing dated receipt files matching the run schedule.

Retiring Manual App Repackaging With AutoPkg Patch Pipelines architecture diagram 2

#Verification and Expected Evidence

Confirm the packaged version against the vendor’s own release notes within 24 hours of a new release appearing in the Jamf Pro Patch Management dashboard. Confirm a live Self Service install on a pilot Mac completes with status Completed and exit code 0 in the Policy Log, not Failed. Confirm the package itself passes Gatekeeper checks locally before it ever leaves the build host.

1codesign --verify --deep --strict /Applications/Google Chrome.app
2spctl --assess --type execute --verbose /Applications/Google Chrome.app

Expected output:

1/Applications/Google Chrome.app: valid on disk
2/Applications/Google Chrome.app: satisfies its Designated Requirement
3/Applications/Google Chrome.app: accepted
4source=Notarized Developer ID

Finally, confirm fleet-wide adoption using a custom extension attribute that reports the installed CFBundleShortVersionString, queried through an Advanced Computer Search after the production ring completes.

#Rollback

Trigger: pilot ring failure rate exceeds five percent, or pilot volunteers report crashes, within the 48-hour dwell window before production promotion. Action: because JamfUploader stores each package under a versioned filename, the rollback script re-points the Patch Policy and Self Service Policy to the previous known-good package ID using a PATCH call against the Jamf Pro API, and hides the newly uploaded package from Self Service visibility. Blast radius is limited by design: because production is gated behind the pilot dwell period, at most the 15 to 30 pilot machines are ever exposed to an unvetted package; the production fleet never receives it until the promotion check passes.

1{
2  "text": "Rollback triggered: Google Chrome pilot ring failure rate 8.3 percent exceeds 5 percent threshold. Reverted Patch Policy 1024 to package version 124.0.6367.91. Production promotion held.",
3  "channel": "#macos-patch-ops",
4  "username": "autopkg-pipeline"
5}

Evidence: Jamf Pro Policy History entry showing the scope and package reverted, with a timestamp matching the Slack rollback confirmation message above.

#Failure and Escalation Conditions

  • Two consecutive nightly runs fail with the same processor error, such as a repeated URLDownloader 404 or a CodeSignatureVerifier mismatch: escalate to an L3 packaging engineer immediately, because this can indicate either a broken vendor URL or a genuine supply-chain signing problem that must not be worked around by disabling verification.
  • Pilot ring failure rate exceeds five percent within the 48-hour dwell window: promotion holds automatically and an L2 escalation channel is notified; a human reviews crash logs before any manual override of the hold.
  • autopkg verify-trust-info returns a non-zero exit code on any recipe: the entire pipeline halts, not just that recipe, because an untrusted diff in a shared recipe repository is treated as a potential compromise until an L3 engineer manually reviews and re-signs trust information.
  • Jamf Pro API authentication failures (401 or 403) occur on more than one consecutive run: escalate to the Jamf Pro administrator to check for credential rotation or scope changes; never substitute a broader admin credential as a workaround.

#Measuring Ticket Deflection

Baseline the monthly count of tickets tagged “software update failed”, “app will not install” and “outdated application” for the 90 days before the pipeline goes live. After rollout, track three numbers weekly: the Jamf Pro Patch Management compliance percentage per title, targeting above 95 percent within five business days of a vendor release; the Self Service install failure rate calculated as failed policy attempts divided by total attempts; and the release-to-deploy latency, meaning the time between a vendor’s published release date and the package reaching the production ring. Under the old manual process this latency was routinely measured in days to weeks per title; a nightly automated pipeline with a 48-hour pilot dwell should bring it under 72 hours consistently. Report all three figures per title in a simple monthly table so that a rising compliance percentage and a falling ticket count are visible side by side to both the support desk and to whoever owns the packaging budget.

#References

For the Gatekeeper and code-signing checks that every automated package must satisfy before deployment, see Apple’s documentation on notarizing macOS software before distribution. For the patch policy, smart group and Self Service scoping behaviour referenced throughout this pipeline, see Jamf’s Jamf Pro documentation on patch management. For teams running the same automation logic against Microsoft Intune instead of Jamf Pro, see Microsoft’s Intune documentation for macOS line-of-business app deployment.

#Operational Context

This pipeline does not run in isolation from the rest of Jamf Pro operations; it competes for the same distribution point bandwidth, API rate limits, and change windows used by OS upgrade campaigns, MDM commands, and other patch policies. Jamf Pro enforces API rate limiting per client, so a nightly AutoPkg run that fans out across fifteen or more recipes in parallel can return 429 responses if it shares a client ID with other automation; in practice the build host should use a dedicated API client separate from any CI job that also queries inventory or triggers MDM commands, and the recipe list should stagger uploads rather than firing all title updates concurrently.

Ownership needs to be explicit before the pipeline goes live, because a fully automated packaging chain removes the natural checkpoint where an L2 technician would have noticed a vendor's licensing change or a renamed installer. A named packaging engineer or small rotation should hold approval rights over the recipe repository, and that same person or rotation should be the first escalation point defined in the Failure and Escalation Conditions above, rather than routing straight to a generic service desk queue that has no context on AutoPkg processor errors.

Distribution point capacity is a frequently overlooked dependency: nightly uploads of full installers for a growing recipe list accumulate storage on Jamf Cloud or an on-premises file share distribution point, and old package versions are not automatically purged unless a retention policy is scripted alongside the pipeline. Teams should decide, before the first production run, how many prior versions of each title are retained for rollback purposes versus how many are purged, since unlimited retention silently grows storage costs while overly aggressive purging can remove the exact rollback target referenced in the Rollback section.

Network egress from the build host is a hard prerequisite that is easy to miss during a change advisory board review: the host must reach each vendor's download CDN directly, not through a web proxy that intercepts TLS and breaks CodeSignatureVerifier's chain-of-trust check. Any corporate proxy or firewall change in front of the build host should be treated as a change that requires re-validating Step 7 of the implementation, since a proxy-injected certificate will cause every signature check to fail closed, which is the correct behaviour but will look identical to a genuine vendor signing problem in the alert channel unless the on-call engineer knows to check network path first.

Because the pipeline runs unattended overnight, its output is only as trustworthy as the alert channel it reports into. Slack or Teams webhook delivery failures are a silent single point of failure: if the webhook URL expires or the channel is archived, a failed run produces no visible signal at all, and the first indication of a problem becomes a rise in help desk tickets weeks later, which defeats the purpose of the automation. The webhook itself should be included in whatever synthetic monitoring or dead-man's-switch check the team already uses for other unattended jobs, so that a missing nightly report is itself alertable.

  • Stagger recipe execution or use separate API clients to avoid Jamf Pro API rate-limit responses when running against a large recipe list.
  • Assign a named owner or rotation for recipe repository approvals; do not default escalation to a general service desk queue unfamiliar with AutoPkg processor errors.
  • Define an explicit package retention policy on the distribution point so rollback targets remain available without unbounded storage growth.
  • Validate that any proxy or firewall change in front of the build host preserves direct, uninterrupted TLS to vendor download endpoints before the next scheduled run.
  • Add the nightly run's alert webhook to existing dead-man's-switch or synthetic monitoring so a missing report is itself a detectable failure.

Evidence trail

Sources and verification

Primary documentation and external technical references used in this article.

  1. 01documentation on notarizing macOS software before distributiondeveloper.apple.com
  2. 02Jamf Pro documentation on patch managementlearn.jamf.com
  3. 03Intune documentation for macOS line-of-business app deploymentlearn.microsoft.com
Isla Morgan

Isla Morgan

Ops Playbook Architect

Isla Morgan is the macOS Platform Engineering Editor for The Ops Playbook, specialising in the design and day-to-day operation of secure, scalable enterprise Mac fleets.

Published
View Profile
Reader Interaction

Comments

Add a thoughtful note on Retiring Manual App Repackaging With AutoPkg Patch Pipelines. Comments are checked for spam and held for moderation before appearing.

Loading comments...
Comment submission is disabled until Cloudflare Turnstile keys are configured.

Discover more

Learn More About KBY

Was this useful?

Operate smarter, with fewer recurring tickets.

Receive new operational playbooks, incident-prevention guidance, automation scripts and recovery runbooks.