{"id":43981137,"url":"https://github.com/macadmins/installapplications","last_synced_at":"2026-02-07T09:03:17.123Z","repository":{"id":44433558,"uuid":"86291943","full_name":"macadmins/installapplications","owner":"macadmins","description":"A tool for dynamically using installapplication","archived":false,"fork":false,"pushed_at":"2026-01-14T17:37:54.000Z","size":872,"stargazers_count":301,"open_issues_count":10,"forks_count":59,"subscribers_count":20,"default_branch":"main","last_synced_at":"2026-01-14T21:35:47.538Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/macadmins.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-03-27T04:40:10.000Z","updated_at":"2026-01-14T17:37:58.000Z","dependencies_parsed_at":"2025-06-16T20:31:17.086Z","dependency_job_id":"2397d594-9908-490a-84e4-2e66729b0756","html_url":"https://github.com/macadmins/installapplications","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/macadmins/installapplications","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macadmins%2Finstallapplications","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macadmins%2Finstallapplications/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macadmins%2Finstallapplications/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macadmins%2Finstallapplications/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/macadmins","download_url":"https://codeload.github.com/macadmins/installapplications/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macadmins%2Finstallapplications/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29190842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T07:37:03.739Z","status":"ssl_error","status_checked_at":"2026-02-07T07:37:03.029Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-07T09:03:15.597Z","updated_at":"2026-02-07T09:03:17.114Z","avatar_url":"https://github.com/macadmins.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# InstallApplications\n\n![InstallApplications icon](/icon/installapplications.png?raw=true)\n\nInstallApplications is an alternative to tools like [PlanB](https://github.com/google/macops-planb) where you can dynamically download packages for use with `InstallApplication`. This is useful for DEP bootstraps, allowing you to have a significantly reduced initial package that can easily be updated without repackaging your initial package.\n\n## Embedded Python\n\nAs of v2.0, InstallApplications now uses its own embedded python v3.8. This is due to Apple's upcoming removal of Python2.\n\nGurl has been updated from the Munki 4.0 release and tested with HTTPs and Basic Authentication. Further testing would be appreciate by the community.\n\n### Embedded Modules\n\nTo help admins with their scripts, the following modules have been added:\nPyObjC (required for gurl)\nRequests (for API driven tools)\n\nShould the need come up for more modules, a PR should be made against the repo with proper justification\n\n### 2to3\n\n`installapplications.py` and `postinstall` have been ran through 2to3 to automatically convert for Python3 compatibility.\n\n### Building embedded python framework\n\nTo reduce the size of the git repository, you **must** create your own Python. To do this, simply run the `./build_python_framework` script within the repository.\n\nThis process was tested on Catalina only.\n\n```\n./build_python_framework\n\nCloning relocatable-python tool from github...\nCloning into '/tmp/relocatable-python-git'...\nremote: Enumerating objects: 20, done.\nremote: Counting objects: 100% (20/20), done.\nremote: Compressing objects: 100% (14/14), done.\nremote: Total 70 (delta 7), reused 16 (delta 6), pack-reused 50\nUnpacking objects: 100% (70/70), done.\nDownloading https://www.python.org/ftp/python/3.8.0/python-3.8.0-macosx10.9.pkg...\n\n...\n\nDone!\nCustomized, relocatable framework is at ./Python.framework\nMoving Python.framework to InstallApplications payload folder\n```\n\n### Package size increases\n\nUnfortunately due to the embedded python, InstallApplications has significantly grown in size, from approximately 35Kb to 27.5 MB. The low size of InstallApplications has traditionally been one of it's greatest strengths, given how fragile `mdmclient` can be, but there is nothing that can be done here.\n\n### Pinning python user/root scripts to embedded Python\n\nPython user/root scripts should be pinned to the embedded Python framework. Moving forward, **scripts not pinned will be unsupported**.\n\nIt is recommended that you run `2to3` against your scripts to make them python3 compliant.\n\n`/usr/local/bin/2to3 -w /path/to/script`\n\nThen simply update the shebang on your python scripts to pin against the InstallApplications python framework.\n\n`#!/Library/installapplications/Python.framework/Versions/Current/bin/python3`\n\nYou can find an example on how this was done by looking at InstallApplications' own `postinstall`\n\n## MDMs that support Custom DEP\n\n- AirWatch\n- FileWave (please contact them for instructions)\n- MicroMDM\n- SimpleMDM\n- Mosyle\n- Jamf School\n- [Fleet](https://fleetdm.com/docs/using-fleet/mdm-macos-setup-experience#bootstrap-package)\n\n### A note about other MDMs\n\nWhile other MDMs could _technically_ install this tool, the mechanism greatly differs. Other MDMs currently use `InstallApplication` API to install their binary. From here, you could then install this tool.\n\nUnfortunately, by doing this, you lose many of the features of `InstallApplications`, the primary one being speed.\n\nExample: Jamf Pro\n\nJamf Pro would install the `jamf` binary first, rather than InstallApplications. An admin would need to scope a policy through the console in order to install this tool and it cannot be 100% validated that InstallApplications will be installed during the SetupAssistant process.\n\n## How this process works:\n\nDuring a DEP SetupAssistant workflow (with a supported MDM), the following will happen:\n\n1. MDM will send a push request utilizing `InstallApplication` to inform the device of a package installation.\n2. InstallApplications (this tool) will install and load its LaunchDaemon.\n2. InstallApplications (this tool) will install and load its LaunchAgent if in the proper context (installed outside of SetupAssistant).\n3. InstallApplications will begin to install your setupassistant packages (if configured) during the SetupAssistant.\n4. If userland packages are configured, InstallApplications will wait until the user is in their active session before installing.\n6. InstallApplications will gracefully exit and kill its process.\n\n## Stages\n\nThere are currently three stages:\n#### preflight ####\n\nThis stage is designed to only work with a **single rootscript**. This stage is useful for running InstallApplications on previously deployed machines or if you simply want to re-run it.\n\nIf the preflight script exits 0, InstallApplications will cleanup/remove itself, bypassing the setupassistant and userland stages.\n\nIf the preflight script exits 1 or higher, InstallApplications will continue with the bootstrap process.\n#### setupassistant ####\n\n- Packages/rootscripts that should be prioritized for download/installation _and_ can be installed during SetupAssistant, where no user session is present.\n\n#### userland ####\n\n- Packages/rootscripts/userscripts that should be prioritized for download/installation but may need to be installed in the user's context. This could be your UI tooling that informs the user that a DEP workflow is being used. This stage will wait for a user session before installing.\n\nBy utilizing setupassistant/userland, you can have **almost instant UI notifications** for your users.\n\n## Notes\n\n- InstallApplications will only begin installing userland when a user session has been started. This is to reduce the likelihood of your packages attempting to start UI elements during SetupAssistant.\n\n### Signing\nYou will **NEED** to sign this package for use with DEP/MDM. To acquire a signing certificate, join the [Apple Developers Program](https://developer.apple.com).\n\nOpen the `build-info.json` file and specify your signing certificate.\n\n```json\n\"signing_info\": {\n    \"identity\": \"Mac Installer: Erik Gomez (XXXXXXXXXXX)\",\n    \"timestamp\": true\n},\n```\n\nNote that you cannot use a `Mac Developer:` signing identity as that is used for application signing and not package signing. Attempting to use this will result in the following error:\n\n`An installer signing identity (not an application signing identity) is required for signing flat-style products.)`\n\n### Downloading and running scripts\n\nInstallApplications can handle downloading and running scripts. Please see below for how to specify the json structure.\n\nFor user scripts, you **must** set the folder path to the `userscripts` sub folder. This is due to the folder having world-wide permissions, allowing the LaunchAgent/User to delete the scripts when finished.\n\n```json\n\"file\": \"/Library/installapplications/userscripts/userland_exampleuserscript.py\",\n```\n\n## Installing InstallApplications to another folder.\n\nIf you need to install IAs to another folder, you can modify the munki-pkg `payload`, but you will also need to modify the launchdaemon plist's `iapath` argument.\n\n```xml\n\u003cstring\u003e--iapath\u003c/string\u003e\n\u003cstring\u003e/Library/installapplications\u003c/string\u003e\n```\n\n### Configuring LaunchAgent/LaunchDaemon for your json\n\nSimply specify a url to your json file in the LaunchDaemon plist, located in the payload/Library/LaunchDaemons folder in the root of the project.\n\n```xml\n\u003cstring\u003e--jsonurl\u003c/string\u003e\n\u003cstring\u003ehttps://domain.tld\u003c/string\u003e\n```\n\nNOTE: If you alter the name of the LaunchAgent/LaunchDaemon or the Label, you will also need enable the arguments `laidentifier` and `ldidentifier` in the launchdaemon plist, and the `lapath` and `ldpath` varibles in the postinstall script.\n\n```xml\n\u003cstring\u003e--laidentifier\u003c/string\u003e\n\u003cstring\u003ecom.example.installapplications\u003c/string\u003e\n\u003cstring\u003e--ldidentifier\u003c/string\u003e\n\u003cstring\u003ecom.example.installapplications\u003c/string\u003e\n```\n\n#### Optional Reboot\n\nIf after installing all of your packages, you want to force a reboot, simply uncomment the flag in the launchdaemon plist.\n\n```xml\n\u003cstring\u003e--reboot\u003c/string\u003e\n```\n\n#### Optional Skip Bootstrap.json validation\n\nIf you would like to pre-package your bootstrap.json file into your package and not download it, simply uncomment the flag in the launchdaemon plist.\n\n```xml\n\u003cstring\u003e--skip-validation\u003c/string\u003e\n```\n\n#### Basic Auth\nCurrently, Basic Authentication is only supported by using `--headers` flag.\n\nThe authentication should be passed as a base64 encoded username:password, including the Basic string.\n\nExample:\n\n```python\nimport base64\n\nbase64.b64encode('test:test')\n'dGVzdDp0ZXN0'\n\nup = base64.b64encode('test:test')\n\nprint 'Basic ' + up\nBasic dGVzdDp0ZXN0\n```\n\nIn the LaunchDaemon add the following:\n\n```xml\n\u003cstring\u003e--headers\u003c/string\u003e\n\u003cstring\u003eBasic dGVzdDp0ZXN0\u003c/string\u003e\n```\n\n#### Follow HTTP Redirects\n\nIf your webserver needs to redirect InstallApplictions to fetch content from another URL, pass `--follow-redirects` in your LaunchDaemon. Useful for situations where content may be stored on a CDN or object storage.\n\n```xml\n\u003cstring\u003e--follow-redirects\u003c/string\u003e\n```\n\n### DEPNotify\n\nAs of InstallApplications v2.0.2, the built in support for DEPNotify has been removed.\n\nBig Sur makes this code less stable. If you would like an example on how to launch DEPNotify with a user script, please see [depnotify_user_launcher.py](https://github.com/erikng/installapplicationsdemo/blob/master/installapplications/scripts/user/depnotify_user_launcher.py) at the installapplications demo GitHub.\n\n### Logging\n\nAll root actions are logged at `/private/var/log/installapplications.log` as well as through NSLog. You can open up Console.app and search for `InstallApplications` to bring up all of the events.\n\nAll user actions are logged at `/var/tmp/installapplications/installapplications.user.log` as well as through NSLog. You can open up Console.app and search for `InstallApplications` to bring up all of the events.\n\n### Middleware\n\nAdapted from Munki's middleware [methodology](https://github.com/munki/munki/wiki/Middleware) and [code](https://github.com/munki/munki/blob/main/code/client/munkilib/fetch.py),\n\nThis optional feature allows an admin to use third party code, or create their own code to manipulate InstallApplication's HTTP requests.\n\n#### Naming\n\nInstallApplications is looking for files the start with \"middleware\".\nExamples of good and bad middleware filenames:\n\n👍  middleware.py  \n👎  middleware  \n👎  my_middleware.py  \n👍  middleware_logic_taken_from_munki.py  \n\n#### Execution\n\nIf you are using middleware, ensure the Python sha-bang is the same as InstallApplications, ie `#!/Library/installapplications/Python.framework/Versions/Current/bin/python3`.\n\n#### Location\n\nThe middleware file must live in the same directory of InstallApplications folder (/Library/installapplications/), including your middleware in `payload/Library/installapplications/` should be sufficient enough to ensure its contained within the build package and receives proper permissions upon install.\n\n#### Requirements\n\n`process_request_options()` is the function that InstallApplications is looking for in the middleware. If InstallApplications doesn't find this function in the middleware it will abandon the processing of the url, and continue on.\n\n#### Middleware Notes\n\n- **Read:** [Munki's wiki page](https://github.com/munki/munki/wiki/Middleware) as this logic was taken directly from Munki, and utilizes the same underlying processes for modifying the url of an item.\n- **URL Overrides:** Install applications allows for the override of some options via the launchdaemon (see [Follow HTTP Redirects](#follow-http-redirects) for an example). The middleware processes items after the launchdaemon specified override is applied, meaning any manipulation to that via the middleware could override the specified options in the Launchd.\n\n### Building a package\n\nThis repository has been setup for use with [munkipkg](https://github.com/munki/munki-pkg). Use `munkipkg` to build your signed installer with the following command:\n\n`./munkipkg /path/to/repository`\n\n### SHA256 hashes\n\nEach package must have a SHA256 hash stored in the JSON. You can easily create hashes with the following command:\n\n`/usr/bin/shasum -a 256 /path/to/pkg`\n\nThis guarantees that the package you place on the web for download is the package that gets installed by InstallApplication. If the hash does not match, InstallApplication will attempt to re-download and re-check.\n\n### JSON Structure\n\nThe JSON structure is quite simple. You supply the following:\n\n- filepath (currently hardcoded to `/Library/installapplications`)\n- url (any domain, but it should ideally be https://)\n- hash (SHA256)\n- name (define a name for the package, for debug logging and DEPNotify)\n- version of package (to check package receipts)\n- package id (to check for package receipts)\n- type of item (currently `rootscript`, `package` or `userscript`)\n- skip_if criteria to skip a pkg (currently `x86_64`, `intel`, `arm64` or `apple_silicon`)\n- retries is the number of times an item is retried to download (defaults to 3 if not set)\n- retrywait is the number of seconds to wait before attempting a retry to download (defaults to 5 if not set)\n\nThe following is an example JSON:\n\n```json\n{\n  \"preflight\": [\n    {\n      \"donotwait\": false,\n      \"file\": \"/Library/installapplications/preflight_script.py\",\n      \"hash\": \"sha256 hash\",\n      \"name\": \"Example Preflight Script\",\n      \"type\": \"rootscript\",\n      \"url\": \"https://domain.tld/preflight_script.py\",\n      \"retries\": 5,\n      \"retrywait\": 10\n    }\n  ],\n  \"setupassistant\": [\n    {\n      \"file\": \"/Library/installapplications/setupassistant.pkg\",\n      \"url\": \"https://domain.tld/setupassistant.pkg\",\n      \"packageid\": \"com.package.setupassistant\",\n      \"version\": \"1.0\",\n      \"hash\": \"sha256 hash\",\n      \"name\": \"setupassistant Package Name\",\n      \"type\": \"package\",\n      \"retries\": 5,\n      \"retrywait\": 10\n    }\n  ],\n  \"userland\": [\n    {\n      \"file\": \"/Library/installapplications/userland.pkg\",\n      \"url\": \"https://domain.tld/userland.pkg\",\n      \"packageid\": \"com.package.userland\",\n      \"version\": \"1.0\",\n      \"hash\": \"sha256 hash\",\n      \"name\": \"Stage 1 Package Name\",\n      \"skip_if\": \"x86_64\",\n      \"type\": \"package\",\n      \"retries\": 5,\n      \"retrywait\": 10\n    },\n    {\n      \"file\": \"/Library/installapplications/userland_examplerootscript.py\",\n      \"hash\": \"sha256 hash\",\n      \"name\": \"Example Script\",\n      \"type\": \"rootscript\",\n      \"url\": \"https://domain.tld/userland_examplerootscript.py\"\n    },\n    {\n      \"file\": \"/Library/installapplications/userscripts/userland_exampleuserscript.py\",\n      \"hash\": \"sha256 hash\",\n      \"name\": \"Example Script\",\n      \"type\": \"userscript\",\n      \"url\": \"https://domain.tld/userland_exampleuserscript.py\"\n    }\n  ]\n}\n```\n\nURLs should not be subject to redirection, or there may be unintended behavior. Please link directly to the URI of the package.\n\nYou may have more than one package and script in each stage. Packages and scripts will be deployed in the order listed.\n\n### Creating your JSON\n\nUsing `generatejson.py` you can automatically generate the json with the file, hash, and name keys populated (you'll need to upload the packages to a server and update the url keys).\n\nYou can pass an unlimited amount of `--item` arguments, each one with the following meta-variables. Please note that currently _all_ of these meta-variables are **required**:\n\n* item-name - required, sets the display name that will show in DEPNotify\n* item-path - required, path on the local disk to the item you want to include\n* item-stage - required, defaults to userland if not specified\n* item-type - required, generatejson will detect package vs script. Scripts default to rootscript, so pass \"userscript\" to this variable if your item is a userscript.\n* item-url - required, if --base-url is set generatejson will auto-generate the URL as base-url/stage/item-file-name. You can override this automatic generation by passing a URL to the item here.\n* script-do-not-wait - required, only applies to userscript and rootscript item-types. Defaults to false.\n* retries - optional, integer value that defaults to 3 if not specified\n* retrywait - optional, integer value that defaults to 5 if not specified\n\nRun the tool:\n\n```\npython generatejson.py --base-url https://github.com --output ~/Desktop \\\n--item \\\nitem-name='preflight' \\\nitem-path='/localpath/preflight.py' \\\nitem-stage='preflight' \\\nitem-type='rootscript' \\\nitem-url='https://github.com/preflight/preflight.py' \\\nscript-do-not-wait=False \\\n--item \\\nitem-name='setupassistant package' \\\nitem-path='/localpath/package.pkg' \\\nitem-stage='setupassistant' \\\nitem-type='package' \\\nitem-url='https://github.com/setupassistant/package.pkg' \\\nscript-do-not-wait=False \\\nretries=5 \\\nretrywait=10 \\\n--item \\\nitem-name='userland user script' \\\nitem-path='/localpath/userscript.py' \\\nitem-stage='userland' \\\nitem-type='userscript' \\\nitem-url='https://github.com/userland/userscript.py' \\\nscript-do-not-wait=True \\\n```\n\nThe bootstrap.json will be saved in the directory specified with `--output`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmacadmins%2Finstallapplications","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmacadmins%2Finstallapplications","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmacadmins%2Finstallapplications/lists"}