fleet/ee/maintained-apps
Allen Houchins e955128d15
Add Sublime Text as macOS FMA (#35339)
Introduces Sublime Text as a maintained app, including input and output
JSON definitions, install/uninstall scripts, and metadata. Adds Sublime
Text icon component and image for frontend display.

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [ ] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [ ] QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

- [ ] Confirmed that the fix is not expected to adversely impact load
test results
- [ ] Alerted the release DRI if additional load testing is needed

## Database migrations

- [ ] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [ ] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [ ] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

## New Fleet configuration settings

- [ ] Setting(s) is/are explicitly excluded from GitOps

If you didn't check the box above, follow this checklist for
GitOps-enabled settings:

- [ ] Verified that the setting is exported via `fleetctl
generate-gitops`
- [ ] Verified the setting is documented in a separate PR to [the GitOps
documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485)
- [ ] Verified that the setting is cleared on the server if it is not
supplied in a YAML file (or that it is documented as being optional)
- [ ] Verified that any relevant UI is disabled when GitOps mode is
enabled

## fleetd/orbit/Fleet Desktop

- [ ] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
- [ ] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes
- [ ] Verified that fleetd runs on macOS, Linux and Windows
- [ ] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))
2025-11-11 21:05:13 -06:00
..
ingesters Omnissa version fix (#32594) 2025-09-04 13:03:59 -04:00
inputs Add Sublime Text as macOS FMA (#35339) 2025-11-11 21:05:13 -06:00
outputs Add Sublime Text as macOS FMA (#35339) 2025-11-11 21:05:13 -06:00
maintained_apps.go Don't overwrite FMA outputs with latest manifest if input has "frozen" set to true (#30044) 2025-06-17 13:10:58 -05:00
README.md Add Windows instructions for FMAs (#34671) 2025-10-24 08:09:51 -04:00
script-diff.sh feat: add CI workflow to automatically check and report script changes in maintained apps (#32006) 2025-08-20 13:09:24 -06:00

Fleet-maintained apps (FMA)

Adding a new app (macOS)

  1. Create a new issue using the New Fleet-maintained app issue template

  2. Find the app's metadata in its Homebrew formulae

  3. Create a new mainfiest 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.

  4. Fill out the file according to the input schema below. For our example Box Drive app, it would look like this:

    {
       "name": "Box Drive",
       "slug": "box-drive/darwin",
       "unique_identifier": "com.box.desktop",
       "token": "box-drive",
       "installer_format": "pkg",
       "default_categories": ["Productivity"]
    }
    
  5. Run the following command from the root of the Fleet repo to generate the app's output data:

    go run cmd/maintained-apps/main.go --slug="<slug-name>" --debug
    
  6. Add a description for the app in outputs/apps.json file. You can use descriptions from Homebrew formulae.

  7. Open a PR to the fleet repository with the above changes. Connect it to the issue by adding Fixes #ISSUE_NUMBER in the description.

  8. The #g-software product group will:

    1. Review the PR and test the app. Contributors should be aware of the validation requirements below.
    2. If validation requirements cannot be met in this PR, the PR will be closed and the associated issue will be prioritized in the g-software group backlog.
  9. If the app passes testing, it is approved and merged. 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. App icon progress is tracked in the issue. An addition to Fleet-maintained apps is not considered "Done" until the icon is added in a Fleet release. This behavior will be improved in a future release.

Input file schema

name (required)

This is the user-facing name of the application.

unique_identifier (required)

This is the platform-specific unique identifier for the app. On macOS, this is the app's bundle identifier.

token (required)

This is the identifier used by homebrew for the app; it is the token field on the homebrew API response.

installer_format (required)

This is the file format for the app's installer. Currently supported values are:

  • zip
  • dmg
  • pkg

To find the app's installer format, you can look at the url field on the homebrew API response. The installer's extension should be at the end of this URL.

Sometimes the file type is not included in the installer's URL. In this case, you can download the installer and use the extension of the downloaded file.

slug (required)

The slug identifies a specific app and platform combination. It is used to name the manifest files that contain the metadata that Fleet needs to add, install, and uninstall this app. This is what is used when referring to the app in GitOps.

The slug is composed of a filesystem-friendly version of the app name, and an operating system platform identifier, separated by a /.

For the app name part, use - to separate words if necessary, for example adobe-acrobat-reader.

Use darwin as the platform part. For example, use a slug of box-drive/darwin for Box Drive on macOS.

pre_uninstall_scripts (optional)

These are command lines that will be run before the generated uninstall script is executed, e.g. for Box.

post_uninstall_scripts (optional)

These are command lines that will be run after the generated uninstall script is executed, e.g. for Box.

default_categories (required)

These are the default categories assigned to the installer in self-service if no categories are specified when it is added to a team's library. Categories must be one or more of these values:

  • Browsers
  • Communication
  • Developer Tools
  • Productivity

install_script_path (optional)

This is a filepath to an install script. If provided, this script will be used instead of the generated install script. Only shell scripts (.sh) are supported.

The script should be added to the inputs/homebrew/scripts directory.

uninstall_script_path (optional)

This is a filepath to an uninstall script. If provided, this script will be used instead of the generated uninstall script. Only shell scripts (.sh) are supported.

uninstall_script_path can't be used together with pre_uninstall_scripts and post_uninstall_scripts.

The script should be added to the inputs/homebrew/scripts directory.

Adding a new app (Windows)

Use the Winget ingester. You will author:

  • An input JSON in ee/maintained-apps/inputs/winget/
  • Optional PowerShell scripts in ee/maintained-apps/inputs/winget/scripts/

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 crossplatform.
  • To find a Winget PackageIdentifier without a Windows host, browse the winget-pkgs repo: https://github.com/microsoft/winget-pkgs (search for your apps manifests).
  • 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).

Step 1: Find the Winget PackageIdentifier

  • On a Windows host, run: winget search <app name>
  • Note the PackageIdentifier. For example, Box Drive is typically Box.Box.

Step 2: Find the unique identifier used by osquery

The Windows ingester expects unique_identifier to match the value in programs.name on the host after install (this is what Fleet uses to confirm the app exists).

  • On a test Windows host, install the app manually, then check either:
    • Fleet live query: SELECT name, version, publisher FROM programs WHERE name LIKE '%<App Name>%';
    • PowerShell: Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | Where-Object {$_.DisplayName -like '*<App Name>*'} | Select-Object DisplayName, DisplayVersion, Publisher
  • Use the exact DisplayName/programs.name string as unique_identifier.

Step 3: Choose installer metadata

If the winget manifest supports multiple installers, these fields select the right one:

  • installer_arch: x64 or x86 (most apps use x64)
  • installer_type: exe, msi, or msix (file type, not vendor tech like "wix")
  • installer_scope: machine or user (prefer machine for managed installs)
  • Optional: installer_locale if a specific locale is required
  • Optional: program_publisher, uninstall_type, fuzzy_match_name (rare)
  • default_categories: one or more of: Browsers, Communication, Developer Tools, Productivity

Tip: Setting these accurately avoids ambiguity when multiple installers exist.

Step 4: Provide install/uninstall scripts

How scripts work:

  • MSI installers: The ingester automatically generates install and uninstall scripts. Do not add scripts unless you need to override the generated behavior.
  • EXE installers: 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.

Place scripts in ee/maintained-apps/inputs/winget/scripts/.

Example install script ee/maintained-apps/inputs/winget/scripts/box_drive_install.ps1:

# Install system-wide, silent
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts

$exeFilePath = "${env:INSTALLER_PATH}"

try {
  # Silent arguments vary by installer (e.g., /S, /silent, /VERYSILENT)
  $processOptions = @{
    FilePath     = "$exeFilePath"
    ArgumentList = "/VERYSILENT /NORESTART"
    PassThru     = $true
    Wait         = $true
  }

  $process = Start-Process @processOptions
  $exitCode = $process.ExitCode

  Write-Host "Install exit code: $exitCode"
  Exit $exitCode
}
catch {
  Write-Host "Error: $_"
  Exit 1
}

Uninstall scripts for EXE installers are vendor-specific. Use the vendors documented silent uninstall switch or the registered UninstallString (if available), ensuring the script runs silently and returns the installers exit code.

For MSI installers, you can omit scripts; for EXE installers, scripts are required.

Step 5: Create the Winget input JSON

Create ee/maintained-apps/inputs/winget/box-drive.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"]
}

Notes:

  • slug uses <app-name>/windows (lowercase, dash-separated name)
  • unique_identifier must match programs.name exactly

Step 6: Generate outputs

From the repo root:

go run cmd/maintained-apps/main.go --slug="box-drive/windows" --debug

This updates/creates:

  • ee/maintained-apps/outputs/apps.json (catalog entry)
  • ee/maintained-apps/outputs/box-drive/windows.json (manifest + script refs)

Step 7: Add description

Edit ee/maintained-apps/outputs/apps.json to add a human-friendly description for your apps entry. For Windows entries, use the vendor description (from the winget manifest or vendor site).

Step 8: Test in a Fleet instance

  • Set an override to point your Fleet instance at your branchs catalog:
export FLEET_DEV_MAINTAINED_APPS_BASE_URL="https://raw.githubusercontent.com/<repository-name>/fleet/refs/heads/<PR-branch-name>/ee/maintained-apps/outputs"
  • Trigger a refresh:
fleetctl trigger --name maintained_apps
  • Add the app to a team, deploy to a Windows host, and verify:
    • Install completes
    • App launches
    • App uninstalls cleanly

Step 9: Open the PR

  • Include “Fixes #” and any validation notes/screenshots
  • The software group will review, validate, and merge when ready

Troubleshooting (Windows)

  • 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 doesnt 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)

Validating Fleet-maintained apps additions

  1. When a pull request (PR) is opened containing changes to ee/maintained-apps/inputs/, the #g-software Product Designer (PD) and Engineering Manager (EM) are automatically added as reviewers.

    1. The PD is responsible for approving the name and default category
    2. The EM is repsonsible for validating or assigning a validator
  2. Ensure an associated issue exists for the PR. If not, create one using the Add Fleet-maintained app issue template. Move the issue to the g-software project and set the status to In Progress. Ensure the PR is linked to the issue.

  3. Validate the PR:

    1. Find the app in Homebrew's GitHub casks and download it locally using cask.url.
    2. Install it on a host and run a live query on the host: SELECT * FROM apps WHERE name LIKE '%App Name%';
    3. Validate and check off items in the Validation section of the issue.
  4. If the PR passes validation, the validator will also execute the test criteria in the QA section of the issue. If tests fail, add feedback in the PR comments. If the test failure(s) cannot be addressed by the contributor, close the PR and move the issue to the Drafting board for prioritization. If tests pass, the PR is approved and merged.

  5. The validator is responsible for adding the icon to Fleet (e.g. the TypeScript and website PNG components of #29175). These can be generated using the generate-icons script.

  6. QA ensures the icon is added to Fleet

Testing additions to Fleet-maintained apps (no icon)

Use the FLEET_DEV_MAINTAINED_APPS_BASE_URL environment variable with the following value:

https://raw.githubusercontent.com/<repository-name>/fleet/refs/heads/<PR-branch-name>/ee/maintained-apps/outputs

Make sure you replace the <PR-branch-name> and <repository-name>

By default, Fleet refreshes the maintained apps catalog on a schedule.
To fetch your branchs catalog immediately (without waiting), run:

fleetctl trigger --name maintained_apps

Test criteria:

  • App adds successfully to team's library
  • App installs successfully on host
  • App opens succuessfully on host
  • App uninstalls successfully on host

If the tests pass:

  • Move issue to Ready (icon addition still needed)
  • Approve and merge PR

If testing fails:

  • Remove issue from the g-software release board, and add issue to the Drafting board. Remove the :release tag and add the :product tag.

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:

  • App can be downloaded using manifest URL
  • App installs successfully on host using manifest install script
  • App exists on host
  • App uninstalls successfully on host using manifest uninstall script

If an app does not pass test criteria:

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:

    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.

    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.