diff --git a/.github/workflows/test-fma-darwin-pr-only.yml b/.github/workflows/test-fma-darwin-pr-only.yml
index 677a3ce3fb..fa7bf8eaa5 100644
--- a/.github/workflows/test-fma-darwin-pr-only.yml
+++ b/.github/workflows/test-fma-darwin-pr-only.yml
@@ -97,6 +97,7 @@ jobs:
if [ -z "$DARWIN_SLUGS" ]; then
echo "has_darwin_apps=false" >> $GITHUB_OUTPUT
echo "has_google_chrome=false" >> $GITHUB_OUTPUT
+ echo "has_fleet_desktop=false" >> $GITHUB_OUTPUT
echo "No darwin apps changed, skipping Darwin workflow"
else
echo "has_darwin_apps=true" >> $GITHUB_OUTPUT
@@ -112,6 +113,14 @@ jobs:
else
echo "has_google_chrome=false" >> $GITHUB_OUTPUT
fi
+
+ # Check if fleet-desktop/darwin is in the changed apps
+ if echo "$DARWIN_SLUGS" | grep -q "^fleet-desktop/darwin$"; then
+ echo "has_fleet_desktop=true" >> $GITHUB_OUTPUT
+ echo "Fleet Desktop detected in changed apps"
+ else
+ echo "has_fleet_desktop=false" >> $GITHUB_OUTPUT
+ fi
fi
shell: bash
@@ -136,6 +145,31 @@ jobs:
sudo rm -rf "$app"
done
+ # Fleet Desktop's installer refuses to run unless the
+ # com.fleetdm.fleetd.config managed preferences profile is present
+ # (it's normally delivered via MDM). CI runners aren't MDM-enrolled,
+ # so we drop a stub plist in place before the validate step so the
+ # install script succeeds. This only runs when fleet-desktop/darwin
+ # is actually being validated in this PR.
+ - name: Create Fleet Desktop MDM config stub (CI-only)
+ if: steps.check-darwin-apps.outputs.has_darwin_apps == 'true' && steps.check-darwin-apps.outputs.has_fleet_desktop == 'true'
+ run: |
+ sudo mkdir -p "/Library/Managed Preferences"
+ sudo tee "/Library/Managed Preferences/com.fleetdm.fleetd.config.plist" > /dev/null <<'PLIST'
+
+
+
+
+ EnrollSecret
+ ci-test-placeholder
+ FleetURL
+ https://ci.test.example.com
+
+
+ PLIST
+ sudo chmod 644 "/Library/Managed Preferences/com.fleetdm.fleetd.config.plist"
+ ls -l "/Library/Managed Preferences/com.fleetdm.fleetd.config.plist"
+
- name: Filter apps.json and verify changed apps
if: steps.check-darwin-apps.outputs.has_darwin_apps == 'true'
run: |
diff --git a/.github/workflows/test-fma-darwin.yml b/.github/workflows/test-fma-darwin.yml
index c4ec4989a6..f7a690e1a1 100644
--- a/.github/workflows/test-fma-darwin.yml
+++ b/.github/workflows/test-fma-darwin.yml
@@ -66,6 +66,30 @@ jobs:
sudo rm -rf "$app"
done
+ # Fleet Desktop's installer refuses to run unless the
+ # com.fleetdm.fleetd.config managed preferences profile is present
+ # (it's normally delivered via MDM). CI runners aren't MDM-enrolled,
+ # so we drop a stub plist in place before the validate step so the
+ # install script succeeds. This workflow validates every FMA every
+ # run, so fleet-desktop is always exercised.
+ - name: Create Fleet Desktop MDM config stub (CI-only)
+ run: |
+ sudo mkdir -p "/Library/Managed Preferences"
+ sudo tee "/Library/Managed Preferences/com.fleetdm.fleetd.config.plist" > /dev/null <<'PLIST'
+
+
+
+
+ EnrollSecret
+ ci-test-placeholder
+ FleetURL
+ https://ci.test.example.com
+
+
+ PLIST
+ sudo chmod 644 "/Library/Managed Preferences/com.fleetdm.fleetd.config.plist"
+ ls -l "/Library/Managed Preferences/com.fleetdm.fleetd.config.plist"
+
- name: Verify Fleet Maintained Apps mac
run: |
ls /Applications
diff --git a/ee/maintained-apps/README.md b/ee/maintained-apps/README.md
index 5cdae701e9..20accfc238 100644
--- a/ee/maintained-apps/README.md
+++ b/ee/maintained-apps/README.md
@@ -46,6 +46,24 @@
| `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/.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/.rb # Cask DSL source
+├── api/.json # Generated with regenerate.sh
+└── regenerate.sh # Rebuild api/*.json from Casks/*.rb
+```
+
+1. Write the cask DSL in `inputs/homebrew/custom-tap/Casks/.rb`.
+2. Run `./regenerate.sh` from inside `custom-tap/` to produce `api/.json`. Requires macOS with Homebrew and `jq`.
+3. In the app's input manifest (`inputs/homebrew/.json`), set `cask_path` to `ee/maintained-apps/inputs/homebrew/custom-tap/api/.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)
diff --git a/ee/maintained-apps/ingesters/homebrew/ingester.go b/ee/maintained-apps/ingesters/homebrew/ingester.go
index 38719a55c8..c53b614bed 100644
--- a/ee/maintained-apps/ingesters/homebrew/ingester.go
+++ b/ee/maintained-apps/ingesters/homebrew/ingester.go
@@ -96,39 +96,9 @@ type brewIngester struct {
}
func (i *brewIngester) ingestOne(ctx context.Context, input inputApp) (*maintained_apps.FMAManifestApp, error) {
- apiURL := fmt.Sprintf("%scask/%s.json", i.baseURL, input.Token)
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
+ cask, err := i.fetchCask(ctx, input)
if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "create http request")
- }
-
- res, err := i.client.Do(req)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "execute http request")
- }
- defer res.Body.Close()
-
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "read http response body")
- }
-
- switch res.StatusCode {
- case http.StatusOK:
- // success, go on
- case http.StatusNotFound:
- return nil, ctxerr.New(ctx, "app not found in brew API")
- default:
- if len(body) > 512 {
- body = body[:512]
- }
- return nil, ctxerr.Errorf(ctx, "brew API returned status %d: %s", res.StatusCode, string(body))
- }
-
- var cask brewCask
- if err := json.Unmarshal(body, &cask); err != nil {
- return nil, ctxerr.Wrapf(ctx, err, "unmarshal brew cask for %s", input.Token)
+ return nil, err
}
out := &maintained_apps.FMAManifestApp{}
@@ -227,6 +197,66 @@ func (i *brewIngester) ingestOne(ctx context.Context, input inputApp) (*maintain
return out, nil
}
+// fetchCask resolves the brew cask JSON for the given input app from
+// either a local file (cask_path) or the default brew API.
+func (i *brewIngester) fetchCask(ctx context.Context, input inputApp) (brewCask, error) {
+ var cask brewCask
+
+ if input.CaskPath != "" {
+ body, err := os.ReadFile(input.CaskPath)
+ if err != nil {
+ return cask, ctxerr.WrapWithData(ctx, err, "reading local cask JSON file", map[string]any{"cask_path": input.CaskPath})
+ }
+ if err := json.Unmarshal(body, &cask); err != nil {
+ return cask, ctxerr.Wrapf(ctx, err, "unmarshal local cask JSON for %s", input.Token)
+ }
+ // Cross-check the cask file matches the configured input. This catches
+ // subtle misconfiguration like pointing cask_path at the wrong JSON file.
+ if cask.Token != input.Token {
+ return cask, ctxerr.Errorf(ctx, "local cask JSON token %q does not match input token %q (cask_path: %s)", cask.Token, input.Token, input.CaskPath)
+ }
+ if len(cask.Name) == 0 {
+ return cask, ctxerr.Errorf(ctx, "local cask JSON for %s has empty name (cask_path: %s)", input.Token, input.CaskPath)
+ }
+ return cask, nil
+ }
+
+ apiURL := fmt.Sprintf("%scask/%s.json", i.baseURL, input.Token)
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
+ if err != nil {
+ return cask, ctxerr.Wrap(ctx, err, "create http request")
+ }
+
+ res, err := i.client.Do(req)
+ if err != nil {
+ return cask, ctxerr.Wrap(ctx, err, "execute http request")
+ }
+ defer res.Body.Close()
+
+ body, err := io.ReadAll(res.Body)
+ if err != nil {
+ return cask, ctxerr.Wrap(ctx, err, "read http response body")
+ }
+
+ switch res.StatusCode {
+ case http.StatusOK:
+ // success, go on
+ case http.StatusNotFound:
+ return cask, ctxerr.New(ctx, "app not found in brew API")
+ default:
+ if len(body) > 512 {
+ body = body[:512]
+ }
+ return cask, ctxerr.Errorf(ctx, "brew API returned status %d: %s", res.StatusCode, string(body))
+ }
+
+ if err := json.Unmarshal(body, &cask); err != nil {
+ return cask, ctxerr.Wrapf(ctx, err, "unmarshal brew cask for %s", input.Token)
+ }
+ return cask, nil
+}
+
type inputApp struct {
// Name is the user-friendly name of the app.
Name string `json:"name"`
@@ -245,6 +275,13 @@ type inputApp struct {
InstallScriptPath string `json:"install_script_path"`
UninstallScriptPath string `json:"uninstall_script_path"`
PatchPolicyPath string `json:"patch_policy_path"`
+ // CaskPath optionally points at a local file (relative to the repo
+ // root) containing the cask JSON in the same schema as
+ // https://formulae.brew.sh/api/cask/.json. Used to commit cask
+ // metadata for third-party taps directly into this repo (see
+ // inputs/homebrew/custom-tap/). When empty, the ingester fetches from
+ // formulae.brew.sh.
+ CaskPath string `json:"cask_path"`
}
type brewCask struct {
diff --git a/ee/maintained-apps/ingesters/homebrew/ingester_test.go b/ee/maintained-apps/ingesters/homebrew/ingester_test.go
index f588ce1156..0cc52b41f0 100644
--- a/ee/maintained-apps/ingesters/homebrew/ingester_test.go
+++ b/ee/maintained-apps/ingesters/homebrew/ingester_test.go
@@ -157,3 +157,106 @@ func TestIngestValidations(t *testing.T) {
})
}
}
+
+// TestIngestCaskPath verifies that when an input app sets cask_path, the
+// ingester reads cask JSON from that local file and makes no HTTP call.
+// This is the path used for casks committed into inputs/homebrew/custom-tap/.
+func TestIngestCaskPath(t *testing.T) {
+ tempDir := t.TempDir()
+
+ caskJSON, err := json.Marshal(brewCask{
+ Token: "local-cask",
+ Name: []string{"Local Cask"},
+ URL: "https://example.com/local/installer.pkg",
+ Version: "9.9.9",
+ SHA256: "deadbeef",
+ })
+ require.NoError(t, err)
+
+ caskPath := path.Join(tempDir, "local-cask.json")
+ require.NoError(t, os.WriteFile(caskPath, caskJSON, 0o644))
+
+ // Server that should never be called when cask_path is set; any hit is a
+ // bug because it means the ingester fell back to HTTP.
+ var httpHits int
+ srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
+ httpHits++
+ }))
+ t.Cleanup(srv.Close)
+
+ ctx := context.Background()
+ i := &brewIngester{
+ logger: slog.New(slog.DiscardHandler),
+ client: fleethttp.NewClient(fleethttp.WithTimeout(10 * time.Second)),
+ baseURL: srv.URL + "/",
+ }
+
+ out, err := i.ingestOne(ctx, inputApp{
+ Token: "local-cask",
+ UniqueIdentifier: "com.example.localcask",
+ InstallerFormat: "pkg",
+ Name: "Local Cask",
+ CaskPath: caskPath,
+ })
+ require.NoError(t, err)
+ require.Equal(t, "https://example.com/local/installer.pkg", out.InstallerURL)
+ require.Equal(t, "9.9.9", out.Version)
+ require.Equal(t, "deadbeef", out.SHA256)
+ require.Equal(t, 0, httpHits, "cask_path path must not make an HTTP call")
+
+ // Missing file yields an actionable error.
+ _, err = i.ingestOne(ctx, inputApp{
+ Token: "missing",
+ UniqueIdentifier: "com.example.missing",
+ InstallerFormat: "pkg",
+ Name: "Missing",
+ CaskPath: path.Join(tempDir, "does-not-exist.json"),
+ })
+ require.ErrorContains(t, err, "reading local cask JSON file")
+ require.Equal(t, 0, httpHits)
+
+ // Token mismatch between input and cask file is rejected so a misconfigured
+ // cask_path can't silently ingest the wrong app.
+ mismatchPath := path.Join(tempDir, "mismatch.json")
+ mismatchJSON, err := json.Marshal(brewCask{
+ Token: "some-other-cask",
+ Name: []string{"Some Other Cask"},
+ URL: "https://example.com/other/installer.pkg",
+ Version: "1.0.0",
+ SHA256: "cafebabe",
+ })
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(mismatchPath, mismatchJSON, 0o644))
+
+ _, err = i.ingestOne(ctx, inputApp{
+ Token: "local-cask",
+ UniqueIdentifier: "com.example.localcask",
+ InstallerFormat: "pkg",
+ Name: "Local Cask",
+ CaskPath: mismatchPath,
+ })
+ require.ErrorContains(t, err, "does not match input token")
+ require.Equal(t, 0, httpHits)
+
+ // Cask file with an empty name is rejected.
+ emptyNamePath := path.Join(tempDir, "empty-name.json")
+ emptyNameJSON, err := json.Marshal(brewCask{
+ Token: "local-cask",
+ Name: []string{},
+ URL: "https://example.com/local/installer.pkg",
+ Version: "9.9.9",
+ SHA256: "deadbeef",
+ })
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(emptyNamePath, emptyNameJSON, 0o644))
+
+ _, err = i.ingestOne(ctx, inputApp{
+ Token: "local-cask",
+ UniqueIdentifier: "com.example.localcask",
+ InstallerFormat: "pkg",
+ Name: "Local Cask",
+ CaskPath: emptyNamePath,
+ })
+ require.ErrorContains(t, err, "empty name")
+ require.Equal(t, 0, httpHits)
+}
diff --git a/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/druva-insync.rb b/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/druva-insync.rb
new file mode 100644
index 0000000000..ebe6fac26f
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/druva-insync.rb
@@ -0,0 +1,40 @@
+cask "druva-insync" do
+ version "7.6.1,110931"
+ sha256 "a67784b4d6789e9a671e2d77789c408116b10c89c6c8893c3f08ed6212684bf2"
+
+ url "https://downloads.druva.com/downloads/inSync/MAC/#{version.csv.first}/inSync-#{version.csv.first}-r#{version.csv.second}.dmg"
+ name "Druva inSync"
+ desc "Endpoint data backup and recovery client"
+ homepage "https://www.druva.com/"
+
+ livecheck do
+ skip "Druva does not expose a parseable version feed; bump manually"
+ end
+
+ depends_on macos: ">= :big_sur"
+
+ pkg "Install inSync.pkg"
+
+ uninstall launchctl: [
+ "com.druva.inSyncAgent",
+ "com.druva.inSyncDecom",
+ "com.druva.inSyncUpgrade",
+ "com.druva.inSyncUpgradeDaemon",
+ ],
+ quit: "com.druva.inSyncClient",
+ pkgutil: "com.druva.inSync.pkg",
+ delete: [
+ "/Library/LaunchAgents/inSyncAgent.plist",
+ "/Library/LaunchAgents/inSyncUpgrade.plist",
+ "/Library/LaunchDaemons/inSyncDecommission.plist",
+ "/Library/LaunchDaemons/inSyncUpgradeDaemon.plist",
+ ]
+
+ zap trash: [
+ "~/Library/Application Support/Druva",
+ "~/Library/Caches/com.druva.inSyncClient",
+ "~/Library/Logs/Druva",
+ "~/Library/Preferences/com.druva.inSyncClient.plist",
+ "~/Library/Saved Application State/com.druva.inSyncClient.savedState",
+ ]
+end
diff --git a/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/fleet-desktop.rb b/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/fleet-desktop.rb
new file mode 100644
index 0000000000..b9925c90bc
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/fleet-desktop.rb
@@ -0,0 +1,36 @@
+cask "fleet-desktop" do
+ version "1.1.0"
+ sha256 "4f3537c37a094f333046072262b1f37729f73074bf935f09f05799bc341fef58"
+
+ url "https://github.com/allenhouchins/fleet-desktop/releases/download/v#{version}/fleet_desktop-v#{version}.pkg"
+ name "Fleet Desktop"
+ desc "End-user client for Fleet device management"
+ homepage "https://github.com/allenhouchins/fleet-desktop"
+
+ livecheck do
+ url :url
+ strategy :github_latest
+ end
+
+ depends_on macos: ">= :ventura"
+
+ pkg "fleet_desktop-v#{version}.pkg"
+
+ uninstall quit: "com.fleetdm.fleet-desktop",
+ pkgutil: "com.fleetdm.fleet-desktop"
+
+ zap trash: [
+ "~/Library/Caches/com.fleetdm.fleet-desktop",
+ "~/Library/HTTPStorages/com.fleetdm.fleet-desktop",
+ "~/Library/HTTPStorages/com.fleetdm.fleet-desktop.binarycookies",
+ "~/Library/Preferences/com.fleetdm.fleet-desktop.plist",
+ "~/Library/Saved Application State/com.fleetdm.fleet-desktop.savedState",
+ "~/Library/WebKit/com.fleetdm.fleet-desktop",
+ ]
+
+ caveats <<~EOS
+ Fleet Desktop requires the Mac to be enrolled in MDM with the
+ com.fleetdm.fleetd.config managed preferences profile. The installer
+ will fail with "Installation Failed" otherwise.
+ EOS
+end
diff --git a/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/zoom-rooms.rb b/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/zoom-rooms.rb
new file mode 100644
index 0000000000..88fd3a0156
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/custom-tap/Casks/zoom-rooms.rb
@@ -0,0 +1,44 @@
+cask "zoom-rooms" do
+ version "7.0.0.12322"
+ sha256 "c35229e6066732aec7f26b762d0f2d43ab7b8270e512fbabe5bbb77a9c714bbc"
+
+ url "https://cdn.zoom.us/prod/#{version}/ZoomRooms.pkg"
+ name "Zoom Rooms"
+ desc "Conference room software for Zoom meetings"
+ homepage "https://www.zoom.com/en/products/zoom-rooms/"
+
+ livecheck do
+ skip "Zoom does not expose a parseable Zoom Rooms version feed; bump manually"
+ end
+
+ depends_on macos: ">= :catalina"
+
+ pkg "ZoomRooms.pkg"
+
+ # The product is branded "Zoom Rooms" but the installer drops the app at
+ # /Applications/ZoomPresence.app with the legacy bundle id us.zoom.ZoomPresence.
+ uninstall launchctl: [
+ "us.zoom.rooms.daemon",
+ "us.zoom.rooms.tool",
+ ],
+ quit: "us.zoom.ZoomPresence",
+ pkgutil: "us.zoom.pkg.zp",
+ delete: [
+ "/Applications/ZoomPresence.app",
+ "/Library/LaunchDaemons/us.zoom.rooms.daemon.plist",
+ "/Library/LaunchDaemons/us.zoom.rooms.tool.plist",
+ "/Library/PrivilegedHelperTools/us.zoom.ZoomRoomsDaemon",
+ "/Library/Logs/us.zoom.ZoomRoomUpdateRecord",
+ "/Library/Logs/zpinstall.log",
+ ]
+
+ zap trash: [
+ "~/Library/Application Support/ZoomPresence",
+ "~/Library/Caches/us.zoom.ZoomPresence",
+ "~/Library/HTTPStorages/us.zoom.ZoomPresence",
+ "~/Library/HTTPStorages/us.zoom.ZoomPresence.binarycookies",
+ "~/Library/Preferences/us.zoom.ZoomPresence.plist",
+ "~/Library/Saved Application State/us.zoom.ZoomPresence.savedState",
+ "~/Library/WebKit/us.zoom.ZoomPresence",
+ ]
+end
diff --git a/ee/maintained-apps/inputs/homebrew/custom-tap/README.md b/ee/maintained-apps/inputs/homebrew/custom-tap/README.md
new file mode 100644
index 0000000000..64ffd4b666
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/custom-tap/README.md
@@ -0,0 +1,85 @@
+# Fleet custom-tap casks
+
+This directory is a **self-contained source of truth** for Fleet-maintained apps that
+don't exist in `Homebrew/homebrew-cask` and therefore can't be ingested from
+`https://formulae.brew.sh/api/`.
+
+It is laid out like a Homebrew tap:
+
+```
+custom-tap/
+├── Casks/ # Cask DSL sources (.rb). Edit these.
+│ ├── fleet-desktop.rb
+│ └── druva-insync.rb
+├── api/ # Generated cask metadata (.json). Do not hand-edit.
+│ ├── fleet-desktop.json
+│ └── druva-insync.json
+├── regenerate.sh # Regenerates api/*.json from Casks/*.rb.
+└── README.md # You are here.
+```
+
+It is **not** a real Homebrew tap — the Fleet repo isn't named
+`homebrew-` and `Casks/` isn't at the repo root, so `brew tap` /
+`brew install` against it won't work. It exists solely to feed Fleet's FMA
+ingester, and keeping both the source (`.rb`) and the built artifact (`.json`)
+in the same repo means the PR that changes a cask also tests the change in
+CI.
+
+## How it hooks into the FMA ingester
+
+Each app here has an input manifest one directory up
+(`../.json`) with a `cask_path` field pointing at the generated JSON.
+Example — [`../fleet-desktop.json`](../fleet-desktop.json):
+
+```json
+{
+ "name": "Fleet Desktop",
+ "token": "fleet-desktop",
+ "cask_path": "ee/maintained-apps/inputs/homebrew/custom-tap/api/fleet-desktop.json",
+ ...
+}
+```
+
+When `go run cmd/maintained-apps/main.go` runs, the ingester sees `cask_path`,
+reads the local file, and skips the brew API entirely. Apps without
+`cask_path` continue to use `https://formulae.brew.sh/api/` as before.
+
+## Adding a new cask
+
+1. Write the cask DSL in `Casks/.rb`. Use an existing file or
+ as a reference.
+2. Run `./regenerate.sh` in this directory to produce `api/.json`.
+3. Create an input manifest at
+ `ee/maintained-apps/inputs/homebrew/.json` that points
+ `cask_path` at the new JSON. Follow the template in
+ `../fleet-desktop.json`.
+4. Generate the FMA output manifest:
+ `go run cmd/maintained-apps/main.go --slug="/darwin"` from the repo
+ root.
+5. Follow the rest of the FMA contributor flow in
+ [`../../../README.md`](../../../README.md) (apps.json description, icon,
+ PR).
+
+## Updating an existing cask
+
+1. Edit the stanza you care about in `Casks/.rb`.
+2. Run `./regenerate.sh` to refresh `api/.json`.
+3. Regenerate the FMA output manifest:
+ `go run cmd/maintained-apps/main.go --slug="/darwin"`.
+4. Commit all three changes together: the `.rb`, the `.json`, and the
+ `outputs//darwin.json`.
+
+## Why `regenerate.sh` strips fields
+
+`brew info --cask --json=v2` includes several fields that depend on the
+developer's machine or the throwaway tap the script uses internally —
+`installed`, `installed_time`, `outdated`, `full_token`, `tap`,
+`tap_git_head`, `generated_date`. None of these are read by the FMA
+ingester, so the script strips them to keep committed JSON stable across
+machines.
+
+## Requirements
+
+- macOS (the `.rb` DSL is parsed by Homebrew).
+- Homebrew installed (`brew` on PATH).
+- `jq` (`brew install jq`).
diff --git a/ee/maintained-apps/inputs/homebrew/custom-tap/api/druva-insync.json b/ee/maintained-apps/inputs/homebrew/custom-tap/api/druva-insync.json
new file mode 100644
index 0000000000..299dda85e5
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/custom-tap/api/druva-insync.json
@@ -0,0 +1,88 @@
+{
+ "token": "druva-insync",
+ "old_tokens": [],
+ "name": [
+ "Druva inSync"
+ ],
+ "desc": "Endpoint data backup and recovery client",
+ "homepage": "https://www.druva.com/",
+ "url": "https://downloads.druva.com/downloads/inSync/MAC/7.6.1/inSync-7.6.1-r110931.dmg",
+ "url_specs": {},
+ "version": "7.6.1,110931",
+ "autobump": true,
+ "no_autobump_message": null,
+ "skip_livecheck": true,
+ "bundle_version": null,
+ "bundle_short_version": null,
+ "sha256": "a67784b4d6789e9a671e2d77789c408116b10c89c6c8893c3f08ed6212684bf2",
+ "artifacts": [
+ {
+ "uninstall": [
+ {
+ "launchctl": [
+ "com.druva.inSyncAgent",
+ "com.druva.inSyncDecom",
+ "com.druva.inSyncUpgrade",
+ "com.druva.inSyncUpgradeDaemon"
+ ],
+ "quit": "com.druva.inSyncClient",
+ "pkgutil": "com.druva.inSync.pkg",
+ "delete": [
+ "/Library/LaunchAgents/inSyncAgent.plist",
+ "/Library/LaunchAgents/inSyncUpgrade.plist",
+ "/Library/LaunchDaemons/inSyncDecommission.plist",
+ "/Library/LaunchDaemons/inSyncUpgradeDaemon.plist"
+ ]
+ }
+ ]
+ },
+ {
+ "pkg": [
+ "Install inSync.pkg"
+ ]
+ },
+ {
+ "zap": [
+ {
+ "trash": [
+ "~/Library/Application Support/Druva",
+ "~/Library/Caches/com.druva.inSyncClient",
+ "~/Library/Logs/Druva",
+ "~/Library/Preferences/com.druva.inSyncClient.plist",
+ "~/Library/Saved Application State/com.druva.inSyncClient.savedState"
+ ]
+ }
+ ]
+ }
+ ],
+ "caveats": null,
+ "caveats_rosetta": null,
+ "depends_on": {
+ "macos": {
+ ">=": [
+ "11"
+ ]
+ }
+ },
+ "conflicts_with": null,
+ "container": null,
+ "rename": [],
+ "auto_updates": null,
+ "deprecated": false,
+ "deprecation_date": null,
+ "deprecation_reason": null,
+ "deprecation_replacement_formula": null,
+ "deprecation_replacement_cask": null,
+ "deprecate_args": null,
+ "disabled": false,
+ "disable_date": null,
+ "disable_reason": null,
+ "disable_replacement_formula": null,
+ "disable_replacement_cask": null,
+ "disable_args": null,
+ "languages": [],
+ "ruby_source_path": "Casks/druva-insync.rb",
+ "ruby_source_checksum": {
+ "sha256": "9443d1939f90512b0c4dc645ef19176e6c44c5bd0119043c6bc38657f9416791"
+ }
+}
diff --git a/ee/maintained-apps/inputs/homebrew/custom-tap/api/fleet-desktop.json b/ee/maintained-apps/inputs/homebrew/custom-tap/api/fleet-desktop.json
new file mode 100644
index 0000000000..6ebeac51a8
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/custom-tap/api/fleet-desktop.json
@@ -0,0 +1,77 @@
+{
+ "token": "fleet-desktop",
+ "old_tokens": [],
+ "name": [
+ "Fleet Desktop"
+ ],
+ "desc": "End-user client for Fleet device management",
+ "homepage": "https://github.com/allenhouchins/fleet-desktop",
+ "url": "https://github.com/allenhouchins/fleet-desktop/releases/download/v1.1.0/fleet_desktop-v1.1.0.pkg",
+ "url_specs": {},
+ "version": "1.1.0",
+ "autobump": true,
+ "no_autobump_message": null,
+ "skip_livecheck": false,
+ "bundle_version": null,
+ "bundle_short_version": null,
+ "sha256": "4f3537c37a094f333046072262b1f37729f73074bf935f09f05799bc341fef58",
+ "artifacts": [
+ {
+ "uninstall": [
+ {
+ "quit": "com.fleetdm.fleet-desktop",
+ "pkgutil": "com.fleetdm.fleet-desktop"
+ }
+ ]
+ },
+ {
+ "pkg": [
+ "fleet_desktop-v1.1.0.pkg"
+ ]
+ },
+ {
+ "zap": [
+ {
+ "trash": [
+ "~/Library/Caches/com.fleetdm.fleet-desktop",
+ "~/Library/HTTPStorages/com.fleetdm.fleet-desktop",
+ "~/Library/HTTPStorages/com.fleetdm.fleet-desktop.binarycookies",
+ "~/Library/Preferences/com.fleetdm.fleet-desktop.plist",
+ "~/Library/Saved Application State/com.fleetdm.fleet-desktop.savedState",
+ "~/Library/WebKit/com.fleetdm.fleet-desktop"
+ ]
+ }
+ ]
+ }
+ ],
+ "caveats": "Fleet Desktop requires the Mac to be enrolled in MDM with the\ncom.fleetdm.fleetd.config managed preferences profile. The installer\nwill fail with \"Installation Failed\" otherwise.\n",
+ "caveats_rosetta": null,
+ "depends_on": {
+ "macos": {
+ ">=": [
+ "13"
+ ]
+ }
+ },
+ "conflicts_with": null,
+ "container": null,
+ "rename": [],
+ "auto_updates": null,
+ "deprecated": false,
+ "deprecation_date": null,
+ "deprecation_reason": null,
+ "deprecation_replacement_formula": null,
+ "deprecation_replacement_cask": null,
+ "deprecate_args": null,
+ "disabled": false,
+ "disable_date": null,
+ "disable_reason": null,
+ "disable_replacement_formula": null,
+ "disable_replacement_cask": null,
+ "disable_args": null,
+ "languages": [],
+ "ruby_source_path": "Casks/fleet-desktop.rb",
+ "ruby_source_checksum": {
+ "sha256": "a348b5c812ce3ee1c1ee361c273ce39725feffb557d48bbda2f42648c1b2e6c5"
+ }
+}
diff --git a/ee/maintained-apps/inputs/homebrew/custom-tap/api/zoom-rooms.json b/ee/maintained-apps/inputs/homebrew/custom-tap/api/zoom-rooms.json
new file mode 100644
index 0000000000..c04656a706
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/custom-tap/api/zoom-rooms.json
@@ -0,0 +1,90 @@
+{
+ "token": "zoom-rooms",
+ "old_tokens": [],
+ "name": [
+ "Zoom Rooms"
+ ],
+ "desc": "Conference room software for Zoom meetings",
+ "homepage": "https://www.zoom.com/en/products/zoom-rooms/",
+ "url": "https://cdn.zoom.us/prod/7.0.0.12322/ZoomRooms.pkg",
+ "url_specs": {},
+ "version": "7.0.0.12322",
+ "autobump": true,
+ "no_autobump_message": null,
+ "skip_livecheck": true,
+ "bundle_version": null,
+ "bundle_short_version": null,
+ "sha256": "c35229e6066732aec7f26b762d0f2d43ab7b8270e512fbabe5bbb77a9c714bbc",
+ "artifacts": [
+ {
+ "uninstall": [
+ {
+ "launchctl": [
+ "us.zoom.rooms.daemon",
+ "us.zoom.rooms.tool"
+ ],
+ "quit": "us.zoom.ZoomPresence",
+ "pkgutil": "us.zoom.pkg.zp",
+ "delete": [
+ "/Applications/ZoomPresence.app",
+ "/Library/LaunchDaemons/us.zoom.rooms.daemon.plist",
+ "/Library/LaunchDaemons/us.zoom.rooms.tool.plist",
+ "/Library/PrivilegedHelperTools/us.zoom.ZoomRoomsDaemon",
+ "/Library/Logs/us.zoom.ZoomRoomUpdateRecord",
+ "/Library/Logs/zpinstall.log"
+ ]
+ }
+ ]
+ },
+ {
+ "pkg": [
+ "ZoomRooms.pkg"
+ ]
+ },
+ {
+ "zap": [
+ {
+ "trash": [
+ "~/Library/Application Support/ZoomPresence",
+ "~/Library/Caches/us.zoom.ZoomPresence",
+ "~/Library/HTTPStorages/us.zoom.ZoomPresence",
+ "~/Library/HTTPStorages/us.zoom.ZoomPresence.binarycookies",
+ "~/Library/Preferences/us.zoom.ZoomPresence.plist",
+ "~/Library/Saved Application State/us.zoom.ZoomPresence.savedState",
+ "~/Library/WebKit/us.zoom.ZoomPresence"
+ ]
+ }
+ ]
+ }
+ ],
+ "caveats": null,
+ "caveats_rosetta": null,
+ "depends_on": {
+ "macos": {
+ ">=": [
+ "10.15"
+ ]
+ }
+ },
+ "conflicts_with": null,
+ "container": null,
+ "rename": [],
+ "auto_updates": null,
+ "deprecated": false,
+ "deprecation_date": null,
+ "deprecation_reason": null,
+ "deprecation_replacement_formula": null,
+ "deprecation_replacement_cask": null,
+ "deprecate_args": null,
+ "disabled": false,
+ "disable_date": null,
+ "disable_reason": null,
+ "disable_replacement_formula": null,
+ "disable_replacement_cask": null,
+ "disable_args": null,
+ "languages": [],
+ "ruby_source_path": "Casks/zoom-rooms.rb",
+ "ruby_source_checksum": {
+ "sha256": "5a428ab919a9c0da0d54f4868ef4ce49693f83a778de7cdd508f063ef770514f"
+ }
+}
diff --git a/ee/maintained-apps/inputs/homebrew/custom-tap/regenerate.sh b/ee/maintained-apps/inputs/homebrew/custom-tap/regenerate.sh
new file mode 100755
index 0000000000..a7915ee73d
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/custom-tap/regenerate.sh
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+#
+# regenerate.sh
+#
+# Rebuild every api/.json from its Casks/.rb source using
+# Homebrew. Run this whenever you edit or add a cask under Casks/, then
+# commit both the updated .rb and the regenerated .json alongside the
+# Fleet-maintained-app output manifest (produced separately by
+# `go run cmd/maintained-apps/main.go --slug=`).
+#
+# Why a throwaway local tap? `brew info --cask --json=v2` can only parse
+# casks reachable through a tap; it won't parse a loose .rb file path.
+# So the script drops the Casks/*.rb into a private tap, runs brew info,
+# extracts the single cask object, and tears the tap down on exit.
+#
+# Fields that vary between developer machines (install state, tap
+# identity, build timestamps) are stripped so the committed JSON is
+# deterministic.
+#
+# Requirements: macOS with Homebrew and jq installed.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$SCRIPT_DIR"
+
+command -v brew >/dev/null 2>&1 || {
+ echo "error: brew is required; install from https://brew.sh" >&2
+ exit 1
+}
+command -v jq >/dev/null 2>&1 || {
+ echo "error: jq is required; install with 'brew install jq'" >&2
+ exit 1
+}
+
+if [ ! -d Casks ]; then
+ echo "error: expected a Casks/ directory next to this script" >&2
+ exit 1
+fi
+
+shopt -s nullglob
+rb_files=(Casks/*.rb)
+if [ ${#rb_files[@]} -eq 0 ]; then
+ echo "error: no .rb files found under Casks/" >&2
+ exit 1
+fi
+
+TAP_USER="fleetdm"
+TAP_NAME="fma-custom-tap"
+TAP_DIR="$(brew --repository)/Library/Taps/${TAP_USER}/homebrew-${TAP_NAME}"
+
+cleanup() {
+ rm -rf "$TAP_DIR"
+}
+trap cleanup EXIT
+
+rm -rf "$TAP_DIR"
+mkdir -p "$TAP_DIR/Casks"
+cp Casks/*.rb "$TAP_DIR/Casks/"
+
+(
+ cd "$TAP_DIR"
+ git init -q
+ git add -A
+ git -c user.email=fma@local -c user.name=fma commit -q -m "local regenerate"
+) >/dev/null
+
+mkdir -p api
+
+# Fields stripped from brew's output because they vary by developer
+# machine and are not read by Fleet's FMA ingester:
+# installed, installed_time, outdated — install state on this host
+# tap, tap_git_head, full_token — tap identity (throwaway tap)
+# generated_date — build timestamp
+STRIP='del(.installed, .installed_time, .outdated, .tap, .tap_git_head, .full_token, .generated_date)'
+
+for rb in "${rb_files[@]}"; do
+ token="$(basename "$rb" .rb)"
+ out="api/${token}.json"
+ echo "Regenerating ${out} from ${rb}..."
+ brew info --cask --json=v2 "${TAP_USER}/${TAP_NAME}/${token}" \
+ | jq ".casks[0] | ${STRIP}" \
+ > "$out"
+done
+
+echo
+echo "Done. If api/*.json changed, also regenerate the FMA output manifests:"
+echo " go run cmd/maintained-apps/main.go --slug=/darwin"
+echo "and commit everything together."
diff --git a/ee/maintained-apps/inputs/homebrew/druva-insync.json b/ee/maintained-apps/inputs/homebrew/druva-insync.json
new file mode 100644
index 0000000000..492faa0c76
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/druva-insync.json
@@ -0,0 +1,9 @@
+{
+ "name": "Druva inSync",
+ "unique_identifier": "com.druva.inSyncClient",
+ "token": "druva-insync",
+ "installer_format": "dmg",
+ "slug": "druva-insync/darwin",
+ "default_categories": ["Productivity"],
+ "cask_path": "ee/maintained-apps/inputs/homebrew/custom-tap/api/druva-insync.json"
+}
diff --git a/ee/maintained-apps/inputs/homebrew/fleet-desktop.json b/ee/maintained-apps/inputs/homebrew/fleet-desktop.json
new file mode 100644
index 0000000000..170998e8eb
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/fleet-desktop.json
@@ -0,0 +1,9 @@
+{
+ "name": "Fleet Desktop",
+ "unique_identifier": "com.fleetdm.fleet-desktop",
+ "token": "fleet-desktop",
+ "installer_format": "pkg",
+ "slug": "fleet-desktop/darwin",
+ "default_categories": ["Productivity"],
+ "cask_path": "ee/maintained-apps/inputs/homebrew/custom-tap/api/fleet-desktop.json"
+}
diff --git a/ee/maintained-apps/inputs/homebrew/schema/input-schema.json b/ee/maintained-apps/inputs/homebrew/schema/input-schema.json
index 57e55988c8..f8f11f1fd4 100644
--- a/ee/maintained-apps/inputs/homebrew/schema/input-schema.json
+++ b/ee/maintained-apps/inputs/homebrew/schema/input-schema.json
@@ -79,6 +79,12 @@
"frozen": {
"type": "boolean",
"description": "If true, the app will not be processed during ingestion and no new output will be created."
+ },
+ "cask_path": {
+ "type": "string",
+ "description": "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/.json. Used to commit cask metadata for third-party taps directly into this repo under inputs/homebrew/custom-tap/.",
+ "minLength": 1,
+ "pattern": "\\.json$"
}
},
"not": {
diff --git a/ee/maintained-apps/inputs/homebrew/zoom-rooms.json b/ee/maintained-apps/inputs/homebrew/zoom-rooms.json
new file mode 100644
index 0000000000..c801c9bbe7
--- /dev/null
+++ b/ee/maintained-apps/inputs/homebrew/zoom-rooms.json
@@ -0,0 +1,9 @@
+{
+ "name": "Zoom Rooms",
+ "unique_identifier": "us.zoom.ZoomPresence",
+ "token": "zoom-rooms",
+ "installer_format": "pkg",
+ "slug": "zoom-rooms/darwin",
+ "default_categories": ["Communication"],
+ "cask_path": "ee/maintained-apps/inputs/homebrew/custom-tap/api/zoom-rooms.json"
+}
diff --git a/ee/maintained-apps/outputs/apps.json b/ee/maintained-apps/outputs/apps.json
index 656d81f639..2ef94923ac 100644
--- a/ee/maintained-apps/outputs/apps.json
+++ b/ee/maintained-apps/outputs/apps.json
@@ -631,6 +631,13 @@
"unique_identifier": "com.getdropbox.dropbox",
"description": "Dropbox is a client for the Dropbox cloud storage service."
},
+ {
+ "name": "Druva inSync",
+ "slug": "druva-insync/darwin",
+ "platform": "darwin",
+ "unique_identifier": "com.druva.inSyncClient",
+ "description": "Druva inSync is a cloud-based product that protects endpoint data."
+ },
{
"name": "Druva inSync",
"slug": "druva-insync/windows",
@@ -729,6 +736,13 @@
"unique_identifier": "Mozilla Firefox 140.7.1 ESR (x64 en-US)",
"description": "Mozilla Firefox ESR is the Extended Support Release version of the popular web browser Firefox."
},
+ {
+ "name": "Fleet Desktop",
+ "slug": "fleet-desktop/darwin",
+ "platform": "darwin",
+ "unique_identifier": "com.fleetdm.fleet-desktop",
+ "description": "Fleet Desktop is a native macOS application that provides end users with a self-service portal for Fleet."
+ },
{
"name": "Fork",
"slug": "fork/darwin",
@@ -1975,6 +1989,13 @@
"unique_identifier": "io.zeplin.osx",
"description": "Zeplin is an app to share, organize, and collaborate on designs."
},
+ {
+ "name": "Zoom Rooms",
+ "slug": "zoom-rooms/darwin",
+ "platform": "darwin",
+ "unique_identifier": "us.zoom.ZoomPresence",
+ "description": "Zoom Rooms is conference room software for Zoom meetings."
+ },
{
"name": "Zoom",
"slug": "zoom/darwin",
diff --git a/ee/maintained-apps/outputs/druva-insync/darwin.json b/ee/maintained-apps/outputs/druva-insync/darwin.json
new file mode 100644
index 0000000000..403e969288
--- /dev/null
+++ b/ee/maintained-apps/outputs/druva-insync/darwin.json
@@ -0,0 +1,22 @@
+{
+ "versions": [
+ {
+ "version": "7.6.1",
+ "queries": {
+ "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.druva.inSyncClient';",
+ "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.druva.inSyncClient' AND version_compare(bundle_short_version, '7.6.1') < 0);"
+ },
+ "installer_url": "https://downloads.druva.com/downloads/inSync/MAC/7.6.1/inSync-7.6.1-r110931.dmg",
+ "install_script_ref": "28323cfd",
+ "uninstall_script_ref": "f2a09666",
+ "sha256": "a67784b4d6789e9a671e2d77789c408116b10c89c6c8893c3f08ed6212684bf2",
+ "default_categories": [
+ "Productivity"
+ ]
+ }
+ ],
+ "refs": {
+ "28323cfd": "#!/bin/sh\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath $INSTALLER_PATH)\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 && \"$console_user\" == \"root\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 && \"$console_user\" == \"root\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Try to launch the application\n if osascript -e \"tell application id \\\"$bundle_id\\\" to activate\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nhdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\"\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\"\n# install pkg files\nquit_and_track_application 'com.druva.inSyncClient'\nsudo installer -pkg \"$TMPDIR/Install inSync.pkg\" -target /\nrelaunch_application 'com.druva.inSyncClient'\n",
+ "f2a09666": "#!/bin/sh\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 && \"$console_user\" == \"root\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/volume<\\/key>/ {getline; gsub(/.*|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/install-location<\\/key>/ {getline; gsub(/.*|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.druva.inSyncAgent'\nremove_launchctl_service 'com.druva.inSyncDecom'\nremove_launchctl_service 'com.druva.inSyncUpgrade'\nremove_launchctl_service 'com.druva.inSyncUpgradeDaemon'\nquit_application 'com.druva.inSyncClient'\nremove_pkg_files 'com.druva.inSync.pkg'\nforget_pkg 'com.druva.inSync.pkg'\nsudo rm -rf '/Library/LaunchAgents/inSyncAgent.plist'\nsudo rm -rf '/Library/LaunchAgents/inSyncUpgrade.plist'\nsudo rm -rf '/Library/LaunchDaemons/inSyncDecommission.plist'\nsudo rm -rf '/Library/LaunchDaemons/inSyncUpgradeDaemon.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Druva'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.druva.inSyncClient'\ntrash $LOGGED_IN_USER '~/Library/Logs/Druva'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.druva.inSyncClient.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.druva.inSyncClient.savedState'\n"
+ }
+}
diff --git a/ee/maintained-apps/outputs/fleet-desktop/darwin.json b/ee/maintained-apps/outputs/fleet-desktop/darwin.json
new file mode 100644
index 0000000000..522133a9f7
--- /dev/null
+++ b/ee/maintained-apps/outputs/fleet-desktop/darwin.json
@@ -0,0 +1,22 @@
+{
+ "versions": [
+ {
+ "version": "1.1.0",
+ "queries": {
+ "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.fleetdm.fleet-desktop';",
+ "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.fleetdm.fleet-desktop' AND version_compare(bundle_short_version, '1.1.0') < 0);"
+ },
+ "installer_url": "https://github.com/allenhouchins/fleet-desktop/releases/download/v1.1.0/fleet_desktop-v1.1.0.pkg",
+ "install_script_ref": "b2592bcc",
+ "uninstall_script_ref": "0c4d343a",
+ "sha256": "4f3537c37a094f333046072262b1f37729f73074bf935f09f05799bc341fef58",
+ "default_categories": [
+ "Productivity"
+ ]
+ }
+ ],
+ "refs": {
+ "0c4d343a": "#!/bin/sh\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 && \"$console_user\" == \"root\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/volume<\\/key>/ {getline; gsub(/.*|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/install-location<\\/key>/ {getline; gsub(/.*|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nquit_application 'com.fleetdm.fleet-desktop'\nremove_pkg_files 'com.fleetdm.fleet-desktop'\nforget_pkg 'com.fleetdm.fleet-desktop'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.fleetdm.fleet-desktop'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.fleetdm.fleet-desktop'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.fleetdm.fleet-desktop.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.fleetdm.fleet-desktop.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.fleetdm.fleet-desktop.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.fleetdm.fleet-desktop'\n",
+ "b2592bcc": "#!/bin/sh\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath $INSTALLER_PATH)\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 && \"$console_user\" == \"root\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 && \"$console_user\" == \"root\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Try to launch the application\n if osascript -e \"tell application id \\\"$bundle_id\\\" to activate\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'com.fleetdm.fleet-desktop'\nsudo installer -pkg \"$TMPDIR/fleet_desktop-v1.1.0.pkg\" -target /\nrelaunch_application 'com.fleetdm.fleet-desktop'\n"
+ }
+}
diff --git a/ee/maintained-apps/outputs/zoom-rooms/darwin.json b/ee/maintained-apps/outputs/zoom-rooms/darwin.json
new file mode 100644
index 0000000000..e3ba41b68e
--- /dev/null
+++ b/ee/maintained-apps/outputs/zoom-rooms/darwin.json
@@ -0,0 +1,22 @@
+{
+ "versions": [
+ {
+ "version": "7.0.0.12322",
+ "queries": {
+ "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'us.zoom.ZoomPresence';",
+ "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'us.zoom.ZoomPresence' AND version_compare(bundle_short_version, '7.0.0.12322') < 0);"
+ },
+ "installer_url": "https://cdn.zoom.us/prod/7.0.0.12322/ZoomRooms.pkg",
+ "install_script_ref": "dc069c1a",
+ "uninstall_script_ref": "e2401991",
+ "sha256": "c35229e6066732aec7f26b762d0f2d43ab7b8270e512fbabe5bbb77a9c714bbc",
+ "default_categories": [
+ "Communication"
+ ]
+ }
+ ],
+ "refs": {
+ "dc069c1a": "#!/bin/sh\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath $INSTALLER_PATH)\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'us.zoom.ZoomPresence'\nsudo installer -pkg \"$TMPDIR/ZoomRooms.pkg\" -target /\nrelaunch_application 'us.zoom.ZoomPresence'\n",
+ "e2401991": "#!/bin/sh\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/volume<\\/key>/ {getline; gsub(/.*|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/install-location<\\/key>/ {getline; gsub(/.*|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'us.zoom.rooms.daemon'\nremove_launchctl_service 'us.zoom.rooms.tool'\nquit_application 'us.zoom.ZoomPresence'\nremove_pkg_files 'us.zoom.pkg.zp'\nforget_pkg 'us.zoom.pkg.zp'\nsudo rm -rf '/Applications/ZoomPresence.app'\nsudo rm -rf '/Library/LaunchDaemons/us.zoom.rooms.daemon.plist'\nsudo rm -rf '/Library/LaunchDaemons/us.zoom.rooms.tool.plist'\nsudo rm -rf '/Library/PrivilegedHelperTools/us.zoom.ZoomRoomsDaemon'\nsudo rm -rf '/Library/Logs/us.zoom.ZoomRoomUpdateRecord'\nsudo rm -rf '/Library/Logs/zpinstall.log'\ntrash $LOGGED_IN_USER '~/Library/Application Support/ZoomPresence'\ntrash $LOGGED_IN_USER '~/Library/Caches/us.zoom.ZoomPresence'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/us.zoom.ZoomPresence'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/us.zoom.ZoomPresence.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/us.zoom.ZoomPresence.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/us.zoom.ZoomPresence.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/us.zoom.ZoomPresence'\n"
+ }
+}
diff --git a/frontend/pages/SoftwarePage/components/icons/FleetDesktop.tsx b/frontend/pages/SoftwarePage/components/icons/FleetDesktop.tsx
new file mode 100644
index 0000000000..564e94a447
--- /dev/null
+++ b/frontend/pages/SoftwarePage/components/icons/FleetDesktop.tsx
@@ -0,0 +1,14 @@
+import * as React from "react";
+
+import type { SVGProps } from "react";
+
+const FleetDesktop = (props: SVGProps) => (
+
+);
+export default FleetDesktop;
diff --git a/frontend/pages/SoftwarePage/components/icons/ZoomRooms.tsx b/frontend/pages/SoftwarePage/components/icons/ZoomRooms.tsx
new file mode 100644
index 0000000000..5f3468a779
--- /dev/null
+++ b/frontend/pages/SoftwarePage/components/icons/ZoomRooms.tsx
@@ -0,0 +1,14 @@
+import * as React from "react";
+
+import type { SVGProps } from "react";
+
+const ZoomRooms = (props: SVGProps) => (
+
+);
+export default ZoomRooms;
diff --git a/frontend/pages/SoftwarePage/components/icons/index.ts b/frontend/pages/SoftwarePage/components/icons/index.ts
index 7ce0c6a4e0..e04f11a6b1 100644
--- a/frontend/pages/SoftwarePage/components/icons/index.ts
+++ b/frontend/pages/SoftwarePage/components/icons/index.ts
@@ -11,6 +11,7 @@ import Charles from "./Charles";
import ConnectFonts from "./ConnectFonts";
import CrashPlan from "./CrashPlan";
import DruvaInSync from "./DruvaInSync";
+import FleetDesktop from "./FleetDesktop";
import Gemini from "./Gemini";
import GoogleCredentialProviderForWindows from "./GoogleCredentialProviderForWindows";
import Iina from "./Iina";
@@ -251,6 +252,7 @@ import Zen from "./Zen";
import Zeplin from "./Zeplin";
import ZeroOneZeroEditor from "./010Editor";
import Zoom from "./Zoom";
+import ZoomRooms from "./ZoomRooms";
import Zotero from "./Zotero";
// SOFTWARE_NAME_TO_ICON_MAP list "special" applications that have a defined
@@ -317,6 +319,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
"company portal": IntuneCompanyPortal,
"connect fonts": ConnectFonts,
crashplan: CrashPlan,
+ "fleet desktop": FleetDesktop,
gemini: Gemini,
"google credential provider for windows": GoogleCredentialProviderForWindows,
iina: Iina,
@@ -515,6 +518,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
zed: Zed,
zen: Zen,
zeplin: Zeplin,
+ "zoom rooms": ZoomRooms,
zotero: Zotero,
} as const;
diff --git a/website/assets/images/app-icon-fleet-desktop-60x60@2x.png b/website/assets/images/app-icon-fleet-desktop-60x60@2x.png
new file mode 100644
index 0000000000..c4707e5bab
Binary files /dev/null and b/website/assets/images/app-icon-fleet-desktop-60x60@2x.png differ
diff --git a/website/assets/images/app-icon-zoom-rooms-60x60@2x.png b/website/assets/images/app-icon-zoom-rooms-60x60@2x.png
new file mode 100644
index 0000000000..28b40a3e87
Binary files /dev/null and b/website/assets/images/app-icon-zoom-rooms-60x60@2x.png differ