mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
This pull request introduces support for ingesting Homebrew casks from third-party taps (not available in the official `Homebrew/homebrew-cask`) into the Fleet Maintained Apps (FMA) system. It does this by allowing cask metadata to be committed directly into the repository and referenced via a new `cask_path` field. The PR also updates CI workflows to better support Fleet Desktop validation and documents the new contributor flow. **Support for custom Homebrew casks:** * Added a new `cask_path` field to app manifests, allowing the FMA ingester to read cask metadata from a local JSON file instead of fetching from the Homebrew API. This enables ingestion of apps from third-party taps or custom casks not present in the official Homebrew repository. [[1]](diffhunk://#diff-be469dd148f0c50ad56489c48bdb514522e1a46d21336e8f747b5880d71a6d1bR49-R66) [[2]](diffhunk://#diff-abd7db4bef16a062c1bd81f54a7c846f1e91b913a9fe9f87976c8075f39b8cd2R270-R276) * Refactored the Homebrew ingester (`brewIngester`) to use a new `fetchCask` helper, which reads from the local file if `cask_path` is set, or falls back to the API otherwise. Includes robust error handling. [[1]](diffhunk://#diff-abd7db4bef16a062c1bd81f54a7c846f1e91b913a9fe9f87976c8075f39b8cd2L99-R101) [[2]](diffhunk://#diff-abd7db4bef16a062c1bd81f54a7c846f1e91b913a9fe9f87976c8075f39b8cd2R200-R251) * Added comprehensive documentation and examples for the custom tap workflow, including a new `custom-tap/` directory with cask DSL sources, generated JSON, and a regeneration script. [[1]](diffhunk://#diff-2dfa2fc79b9becad555db38289a16afe4ce651665a31868d386fed8b4e160740R1-R85) [[2]](diffhunk://#diff-be469dd148f0c50ad56489c48bdb514522e1a46d21336e8f747b5880d71a6d1bR49-R66) * Added new custom casks for `fleet-desktop`, `druva-insync`, and `zoom-rooms` under `inputs/homebrew/custom-tap/Casks/`. [[1]](diffhunk://#diff-2555a54830de2bfb0ffca8bc487aac67de84dee5d431fe5f42e90e1754f63bb6R1-R36) [[2]](diffhunk://#diff-db1fa8a43a27c5adf49a5ade04e61405ce1e9420f266e3160156cabf69ed4ea8R1-R40) [[3]](diffhunk://#diff-effd461583140683d41dc68d9a93692d039be5ad5e52b6b108ece79f17155107R1-R44) **Testing and validation:** * Added a new test (`TestIngestCaskPath`) to ensure the ingester correctly reads from `cask_path` and does not make unnecessary HTTP requests, with error handling for missing files. **CI workflow improvements:** * Updated GitHub Actions workflows to handle Fleet Desktop's installer requirements in CI by creating a managed preferences stub when validating Fleet Desktop, ensuring the installer succeeds even without MDM enrollment. [[1]](diffhunk://#diff-28b30c8601cb7662d59efbfbbcf800cae91455fd3d875627659dced8c1257a24R100) [[2]](diffhunk://#diff-28b30c8601cb7662d59efbfbbcf800cae91455fd3d875627659dced8c1257a24R116-R123) [[3]](diffhunk://#diff-28b30c8601cb7662d59efbfbbcf800cae91455fd3d875627659dced8c1257a24R148-R172) [[4]](diffhunk://#diff-c263ffc3062c3b5e4e4eb65976080c6cbddac478a5fed3392fe8b23c49bb2da8R69-R92) These changes make it possible to maintain and test apps from custom Homebrew taps within the Fleet repo, improving flexibility and reliability for Fleet-maintained apps. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for three new macOS apps: Fleet Desktop, Druva inSync, and Zoom Rooms * Added UI icons for Fleet Desktop and Zoom Rooms * **Enhancements** * Fleet Desktop includes an MDM enrollment caveat and improved installer validation for macOS installers * Support for overriding Homebrew cask input via a local cask JSON file * **Tests** * Added unit coverage for local cask JSON ingestion behavior * **Chores** * Added a deterministic script to regenerate Homebrew custom-tap manifests <!-- end of auto-generated comment: release notes by coderabbit.ai -->
182 lines
18 KiB
Markdown
182 lines
18 KiB
Markdown
# Fleet-maintained apps (FMA)
|
||
|
||
## Adding a new app (macOS)
|
||
|
||
1. Find the app's metadata in its [Homebrew formulae](https://formulae.brew.sh/)
|
||
2. Create a new manifest file called `$YOUR_APP_NAME.json` in the `inputs/homebrew/` directory. For
|
||
example, if you wanted to add Box Drive, create the file `inputs/homebrew/box-drive.json`.
|
||
3. Fill out the file according to the [input schema below](#macos-input-file-schema). For our example Box Drive app, it would look like this:
|
||
|
||
```json
|
||
{
|
||
"name": "Box Drive",
|
||
"slug": "box-drive/darwin",
|
||
"unique_identifier": "com.box.desktop",
|
||
"token": "box-drive",
|
||
"installer_format": "pkg",
|
||
"default_categories": ["Productivity"]
|
||
}
|
||
```
|
||
|
||
4. Run the following command from the root of the Fleet repo to generate the app's output data:
|
||
|
||
```bash
|
||
go run cmd/maintained-apps/main.go --slug="<slug-name>" --debug
|
||
```
|
||
|
||
5. The contributor is responsible for adding the icon to Fleet (e.g. the TypeScript and website PNG components of [#29175](https://github.com/fleetdm/fleet/pull/29175/files)). These are generated using the [generate-icons](https://github.com/fleetdm/fleet/tree/main/tools/software/icons) script. **The script automatically adds the import statement and map entry to `frontend/pages/SoftwarePage/components/icons/index.ts`**, so you don't need to manually update the index file.
|
||
|
||
6. Add a description for the app in `outputs/apps.json` file. You can use descriptions from [Homebrew formulae](https://formulae.brew.sh/). For consistency and presentation on the website, the description should follow sentence casing and the following format: `<App Name>` is a(n) (copy description from Homebrew)., making sure to end with a `.`.
|
||
|
||
7. Open a PR to the `fleet` repository with the above changes. The [#g-software Engineering Manager (EM)](https://fleetdm.com/handbook/company/product-groups#software-group) is automatically added reviewer. Also, @ mention the [Fleet-maintained apps DRI](https://fleetdm.com/handbook/company/communications#:~:text=Fleet%2Dmaintained%20apps).
|
||
|
||
8. If the app passes automated tests, it is approved and merged. The EM reviews the PR within 3 business days. The app should appear shortly in the Fleet-maintained apps section when adding new software to Fleet. The app icon will not appear in Fleet until the following release.
|
||
|
||
### macOS input file schema
|
||
|
||
| Name | Type | Description |
|
||
|--------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||
| `name` | string | **Required.** User-facing name of the application. |
|
||
| `unique_identifier` | string | **Required.** Platform-specific unique identifier (e.g., bundle identifier on macOS). |
|
||
| `token` | string | **Required.** Homebrew's unique identifier. It's the `token` field of the Homebrew API response. |
|
||
| `installer_format` | string | **Required.** File format of the installer (`zip`, `dmg`, `pkg`). Determine via the file extension in the Homebrew API `url` field or by downloading the installer if the extension isn’t present. |
|
||
| `slug` | string | **Required.** Identifies the app/platform combination (e.g., `box-drive/darwin`). Used to name manifest files and reference the app in [Fleet's best practice GitOps](https://fleetdm.com/docs/configuration/yaml-files#fleet-maintained-apps). Format: `<app-name>/<platform>`, where app name is filesystem-friendly and platform is `darwin`. |
|
||
| `default_categories` | string | **Required.** Default categories for self-service if none are specified. Valid values: `Browsers`, `Communication`, `Developer Tools`, `Productivity`. |
|
||
| `pre_uninstall_scripts` | string | Command lines run **before** the generated uninstall script (e.g., for [Box](inputs/homebrew/box-drive.json)). |
|
||
| `post_uninstall_scripts` | string | Command lines run **after** the generated uninstall script (e.g., for [Box](inputs/homebrew/box-drive.json)). |
|
||
| `install_script_path` | string | Filepath to a custom install script (`.sh`). Overrides the generated install script. Script must be placed in `inputs/homebrew/scripts/`. |
|
||
| `uninstall_script_path` | string | Filepath to a custom uninstall script (`.sh`). Overrides the generated uninstall script. Cannot be used together with `pre_uninstall_scripts` or `post_uninstall_scripts`. Script must be placed in `inputs/homebrew/scripts/`. |
|
||
| `cask_path` | string | Path (relative to the repo root) to a local file containing the cask JSON in the same schema as `https://formulae.brew.sh/api/cask/<token>.json`. Used to commit cask metadata for third-party taps directly into this repo under [`inputs/homebrew/custom-tap/`](inputs/homebrew/custom-tap/). See [Ingesting apps from a custom tap](#ingesting-apps-from-a-custom-tap) below. |
|
||
|
||
### Ingesting apps from a custom tap
|
||
|
||
Apps that live in a third-party Homebrew tap (not `Homebrew/homebrew-cask`) are not proxied by `https://formulae.brew.sh/api/`. To ingest them, commit both the `.rb` source and the generated `.json` into [`inputs/homebrew/custom-tap/`](inputs/homebrew/custom-tap/), laid out like a Homebrew tap:
|
||
|
||
```
|
||
custom-tap/
|
||
├── Casks/<token>.rb # Cask DSL source
|
||
├── api/<token>.json # Generated with regenerate.sh
|
||
└── regenerate.sh # Rebuild api/*.json from Casks/*.rb
|
||
```
|
||
|
||
1. Write the cask DSL in `inputs/homebrew/custom-tap/Casks/<token>.rb`.
|
||
2. Run `./regenerate.sh` from inside `custom-tap/` to produce `api/<token>.json`. Requires macOS with Homebrew and `jq`.
|
||
3. In the app's input manifest (`inputs/homebrew/<token>.json`), set `cask_path` to `ee/maintained-apps/inputs/homebrew/custom-tap/api/<token>.json`. See `inputs/homebrew/fleet-desktop.json` for an example.
|
||
|
||
See [`inputs/homebrew/custom-tap/README.md`](inputs/homebrew/custom-tap/README.md) for the full contributor flow. Apps without `cask_path` continue to be fetched from `formulae.brew.sh`.
|
||
|
||
## Adding a new app (Windows)
|
||
|
||
1. Find the Winget `PackageIdentifier` in the relevant [winget-pkgs repo manifest](https://github.com/microsoft/winget-pkgs/tree/master/manifests).
|
||
|
||
2. Get the unique identifier that Fleet will use for matching the software with software inventory:
|
||
- On a test Windows host, install the app manually, then run the following PowerShell script that correlates to the defined `installer_scope`:
|
||
- Machine scope: `Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -like '*<App Name>*'} | Select-Object DisplayName, DisplayVersion, Publisher`
|
||
- User scope: `Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -like '*<App Name>*'} | Select-Object DisplayName, DisplayVersion, Publisher`
|
||
|
||
If the `unique_identifier` doesn't match the `DisplayName`, then Fleet will incorrectly create two software titles when the Fleet-maintained app is added and later installed. One title for the Fleet-maintained app and a separate title for the inventoried software.
|
||
|
||
3. Fill out the file according to the [input schema](#windows-input-file-schema). For example, Box Drive looks like this:
|
||
|
||
```json
|
||
{
|
||
"name": "Box Drive",
|
||
"slug": "box-drive/windows",
|
||
"package_identifier": "Box.Box",
|
||
"unique_identifier": "Box",
|
||
"installer_arch": "x64",
|
||
"installer_type": "msi",
|
||
"installer_scope": "machine",
|
||
"default_categories": ["Productivity"]
|
||
}
|
||
```
|
||
|
||
|
||
4. Run `go run cmd/maintained-apps/main.go --slug="<app-name>/windows" --debug` from the root of the
|
||
Fleet repo to generate the app's output data, replacing `<app-name>` with your app's name, for example:
|
||
|
||
```bash
|
||
go run cmd/maintained-apps/main.go --slug="box-drive/windows" --debug
|
||
```
|
||
|
||
5. The contributor is responsible for adding the icon to Fleet (e.g. the TypeScript and website PNG components of [#29175](https://github.com/fleetdm/fleet/pull/29175/files)). These are generated using the [generate-icons](https://github.com/fleetdm/fleet/tree/main/tools/software/icons) script. **The script automatically adds the import statement and map entry to `frontend/pages/SoftwarePage/components/icons/index.ts`**, so you don't need to manually update the index file.
|
||
|
||
6. Add a description for the app in outputs/apps.json file. You can use descriptions from the wingest manifest.
|
||
|
||
7. Open a PR to the fleet repository with the above changes. The [#g-software Engineering Manager (EM)](https://fleetdm.com/handbook/company/product-groups#software-group) is automatically added reviewer. Also, @ mention the #g-software Product Designer (PD) in a comment that points them to the new icon. This way, the icon change gets a second pair of eyes.
|
||
|
||
8. If the app passes automated tests, it is approved and merged. The EM reviews the PR within 3 business days. The app should appear shortly in the Fleet-maintained apps section when adding new software to Fleet. The app icon will not appear in Fleet until the following release.
|
||
|
||
### Windows input file schema
|
||
|
||
| Name | Type | Description |
|
||
|--------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||
| `name` | string | **Required.** User-facing name of the application. |
|
||
| `unique_identifier` | string | **Required.** Platform-specific unique identifier. For Windows, this is the `DisplayName`. |
|
||
| `package_identifier` | string | **Required.** The `PackageIdentifier` from winget. Fleet uses this to pull the correct metadata for the app. |
|
||
| `slug` | string | **Required.** Identifies the app/platform combination (e.g., `box-drive/windows`). Used to name manifest files and reference the app in [Fleet's best practice GitOps](https://fleetdm.com/docs/configuration/yaml-files#fleet-maintained-apps). Format: `<app-name>/<platform>`, where app name is filesystem-friendly and platform is `darwin`. |
|
||
| `installer_arch` | string | **Required.** `x64` or `x86` (most apps use `x64`). |
|
||
| `installer_type` | string | **Required.** `exe`, `msi`, or `msix` (file type, not vendor tech like "wix") |
|
||
| `installer_scope` | string | **Required.** `machine` or `user` (prefer `machine` for managed installs) |
|
||
| `default_categories` | string | **Required.** Default categories for self-service if none are specified. Valid values: `Browsers`, `Communication`, `Developer Tools`, `Productivity`. |
|
||
| `install_script_path` | string | Filepath to a custom install script (`.ps1`). Overrides the generated install script. Script must be placed in `inputs/winget/scripts/`. For `.msi` apps, the ingestor automatically generates install scripts. Do not add scripts unless you need to override the generated behavior. For `.exe` apps, you must provide PowerShell scripts that run the installer file directly. Fleet stores the installer and sends it to the host at install time; your script must execute it using the `INSTALLER_PATH` environment variable. |
|
||
| `uninstall_script_path` | string | Filepath to a custom uninstall script (`.ps1`). Overrides the generated uninstall script. Script must be placed in `inputs/winget/scripts/`. For `.msi` apps, the ingestor automatically generates uninstall scripts. Do not add scripts unless you need to override the generated behavior. For `.exe` apps, you must provide a script to uninstall the app. Scripts for `.exe` apps are vendor-specific. Use the vendor’s documented silent uninstall switch or the registered UninstallString (if available), ensuring the script runs silently and returns the installer’s exit code. |
|
||
| `fuzzy_match_name` | boolean | If the `unique_identifier` doesn't match the `DisplayName`, use `fuzzy_match_name` to specify that Fleet uses "fuzzy matching" to match the Fleet-maintained app and the inventoried software. For example, for Pritunl, the `unique_identifier` is "Pritunl" and the inventories software's `DisplayName` is "Pritunl Client". With `fuzzy_match_name` set to true, Pritunl app will be matched to the inventories software. |
|
||
|
||
#### Windows troubleshooting
|
||
|
||
- App not found in Fleet UI: ensure `apps.json` was updated by the generator and your override URL is correct
|
||
- Install fails silently: confirm your `installer_type`, `installer_arch`, and `installer_scope` match the selected winget installer; run your PowerShell script manually on a test host
|
||
- Uninstall doesn’t remove the app: prefer explicit uninstall scripts; otherwise, ensure the winget manifest exposes `ProductCode` or `UpgradeCode`
|
||
- Hash mismatch errors: if the upstream manifest is in flux, you can set `ignore_hash: true` in the input JSON (use sparingly)
|
||
|
||
#### Can I do this on macOS?
|
||
|
||
The instructions below are meant to be run on a Windows host. But, you can run most of this on a macOS host, as well:
|
||
- You can author Windows inputs and run the generator on macOS. The ingester is Go code that fetches data from winget/GitHub and works cross‑platform.
|
||
- To find the PackageName and Publisher, you can look in the locale and installer yaml files in the winget-pkgs repo.
|
||
- Validation and testing still require a Windows host (to verify programs.name and to run install/uninstall).
|
||
|
||
## Updating existing Fleet-maintained apps
|
||
|
||
Fleet-maintained apps need to be updated as frequently as possible while maintaining reliability. This is currently a balancing act as both scenarios below result in customer workflow blocking bugs:
|
||
|
||
- App vendor updates to installers can break install/uninstall scripts
|
||
- App vendors will deprecate download links for older installers
|
||
|
||
A Github action periodically creates a PR that updates one or more apps in the catalog by:
|
||
|
||
- Bumping versions
|
||
- Regenerating install/uninstall scripts
|
||
|
||
Each app updated in the PR must be validated independently. Only merge the PR if all apps changed meet the following criteria:
|
||
|
||
- [X] App can be downloaded using manifest URL
|
||
- [X] App installs successfully on host using manifest install script
|
||
- [X] App exists on host
|
||
- [X] App uninstalls successfully on host using manifest uninstall script
|
||
|
||
If an app does not pass test criteria:
|
||
|
||
- [Freeze the app](#freezing-an-existing-fleet-maintained-app)
|
||
- File a bug for tracking
|
||
|
||
## Freezing an existing Fleet-maintained app
|
||
|
||
If any app fails validation:
|
||
|
||
1. Do not merge the PR as-is.
|
||
2. Add `"frozen": true"` to the failing app's input file (e.g.,`inputs/homebrew/<app>.json`).
|
||
3. Revert its corresponding output manifest file (e.g., `outputs/<slug>.json`) to the version in the `main` branch:
|
||
|
||
```bash
|
||
git checkout origin/main -- ee/maintained-apps/outputs/<slug>.json
|
||
```
|
||
|
||
4. Validate changes in the frozen input file by running the following. This should output no errors and generate no changes.
|
||
|
||
```bash
|
||
go run cmd/maintained-apps/main.go --slug="<slug>" --debug
|
||
```
|
||
|
||
5. Commit both the input change and the output file revert to the same PR.
|