Skip to content

Update Fleet-maintained apps - #49786

Closed
fleet-release wants to merge 1 commit into
mainfrom
fma-2607222041
Closed

Update Fleet-maintained apps#49786
fleet-release wants to merge 1 commit into
mainfrom
fma-2607222041

Conversation

@fleet-release

@fleet-release fleet-release commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Automated ingestion of latest Fleet-maintained app data.

Summary by CodeRabbit

  • New Features
    • Added support for the latest releases of numerous maintained applications across macOS and Windows, including browsers, productivity tools, developer utilities, and communication apps.
    • Updated downloadable installers and verification data to match current releases.
  • Bug Fixes
    • Improved upgrade detection so installed applications are correctly recognized as outdated.
    • Refined removal behavior for select apps, including more complete cleanup of related files and services.

Generated automatically with cmd/maintained-apps.
@github-actions

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/advanced-installer/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/akiflow/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/beyond-compare/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/brave-browser/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/cleanmymac/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/clop/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/comet/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/cursor/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/dataflare/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/dataflare/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/firefox@developer-edition/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/firefox@nightly/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/granola/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/granola/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/lookaway/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/loom/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/loom/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/macwhisper/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/marsedit/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/megasync/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/microsoft-teams/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/microsoft-teams/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/nosql-workbench/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/nosql-workbench/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/obs/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/only-switch/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/popclip/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/powerphotos/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/powershell/windows.json

=== Install Script (no changes) ===
=== Uninstall // 0995d374 -> 06cf76b8 ===

--- /tmp/old.kwogk0	2026-07-22 20:49:10.672709438 +0000
+++ /tmp/new.U1sKJE	2026-07-22 20:49:10.672709438 +0000
@@ -1,4 +1,4 @@
-$product_code = '{7B031DCF-BDCE-47D6-89B9-4C558D76E773}'
+$product_code = '{92D9A5DC-8C64-40D5-B1BC-98DB9C7FDB7F}'
 $timeoutSeconds = 300  # 5 minute timeout
 
 # Fleet uninstalls app using product code that's extracted on upload

ee/maintained-apps/outputs/prisma-browser/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/pritunl/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/pritunl/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/rider/darwin.json

=== Install Script (no changes) ===
=== Uninstall // c52dae57 -> 22db20ac ===

--- /tmp/old.yuZ39a	2026-07-22 20:49:10.868711168 +0000
+++ /tmp/new.iNW79x	2026-07-22 20:49:10.868711168 +0000
@@ -54,9 +54,9 @@
 
 sudo rm -rf "$APPDIR/Rider.app"
 sudo rm -rf 'rider'
-trash $LOGGED_IN_USER '~/Library/Application Support/Rider2026.1'
-trash $LOGGED_IN_USER '~/Library/Caches/Rider2026.1'
-trash $LOGGED_IN_USER '~/Library/Logs/Rider2026.1'
+trash $LOGGED_IN_USER '~/Library/Application Support/Rider2026.2'
+trash $LOGGED_IN_USER '~/Library/Caches/Rider2026.2'
+trash $LOGGED_IN_USER '~/Library/Logs/Rider2026.2'
 trash $LOGGED_IN_USER '~/Library/Preferences/jetbrains.rider.71e559ef.plist'
-trash $LOGGED_IN_USER '~/Library/Preferences/Rider2026.1'
+trash $LOGGED_IN_USER '~/Library/Preferences/Rider2026.2'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.jetbrains.rider.savedState'

ee/maintained-apps/outputs/rustrover/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/setapp/darwin.json

=== Install Script (no changes) ===
=== Uninstall // e23bd29d -> 9ef05eb6 ===

--- /tmp/old.5V246R	2026-07-22 20:49:10.968712050 +0000
+++ /tmp/new.7cUrSj	2026-07-22 20:49:10.968712050 +0000
@@ -5,6 +5,76 @@
 LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
 # functions
 
+remove_launchctl_service() {
+  local service="$1"
+  local booleans=("true" "false")
+  local plist_status
+  local paths
+  local should_sudo
+
+  echo "Removing launchctl service ${service}"
+
+  # A wildcard label can't be used with launchctl or as a plist name, so expand
+  # it to the labels of currently loaded services that match the pattern.
+  local services=("$service")
+  if [[ "$service" == *"*"* ]]; then
+    local regex
+    # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so
+    # it matches a full label rather than a substring.
+    regex=$(printf '%s' "$service" | sed -e 's/[][(){}.^$+?|\\]/\\&/g' -e 's/\*/.*/g')
+    regex="^${regex}$"
+    services=()
+    local id
+    # Match every loaded job by label regardless of PID; launchctl list reports
+    # loaded-but-not-running jobs with a "-" in the PID column.
+    while read -r _ _ id; do
+      [[ "$id" =~ $regex ]] && services+=("$id")
+    done < <(launchctl list 2>/dev/null | tail -n +2)
+    if [[ ${#services[@]} -eq 0 ]]; then
+      echo "No loaded launchctl service matches ${service}"
+      return
+    fi
+  fi
+
+  local service_label
+  for service_label in "${services[@]}"; do
+    for should_sudo in "${booleans[@]}"; do
+      plist_status=$(launchctl list "${service_label}" 2>/dev/null)
+
+      if [[ $plist_status == \{* ]]; then
+        if [[ $should_sudo == "true" ]]; then
+          sudo launchctl remove "${service_label}"
+        else
+          launchctl remove "${service_label}"
+        fi
+        sleep 1
+      fi
+
+      paths=(
+        "/Library/LaunchAgents/${service_label}.plist"
+        "/Library/LaunchDaemons/${service_label}.plist"
+      )
+
+      # if not using sudo, prepend the home directory to the paths
+      if [[ $should_sudo == "false" ]]; then
+        for i in "${!paths[@]}"; do
+          paths[i]="${HOME}${paths[i]}"
+        done
+      fi
+
+      for path in "${paths[@]}"; do
+        if [[ -e "$path" ]]; then
+          if [[ $should_sudo == "true" ]]; then
+            sudo rm -f -- "$path"
+          else
+            rm -f -- "$path"
+          fi
+        fi
+      done
+    done
+  done
+}
+
 trash() {
   local logged_in_user="$1"
   local target_file="$2"
@@ -52,10 +122,19 @@
   fi
 }
 
+remove_launchctl_service 'com.setapp.DesktopClient.SetappAgent'
+remove_launchctl_service 'com.setapp.DesktopClient.SetappAssistant'
+remove_launchctl_service 'com.setapp.DesktopClient.SetappLauncher'
+remove_launchctl_service 'com.setapp.DesktopClient.SetappUpdater'
 sudo rm -rf "$APPDIR/Setapp.app"
 trash $LOGGED_IN_USER '~/Library/Application Scripts/com.setapp.DesktopClient.SetappAgent.FinderSyncExt'
+trash $LOGGED_IN_USER '~/Library/Application Support/Setapp*'
 trash $LOGGED_IN_USER '~/Library/Caches/com.setapp.DesktopClient'
 trash $LOGGED_IN_USER '~/Library/Caches/com.setapp.DesktopClient.SetappAgent'
+trash $LOGGED_IN_USER '~/Library/Containers/com.setapp.DesktopClient.SetappAgent.FinderSyncExt'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.setapp.DesktopClient*'
+trash $LOGGED_IN_USER '~/Library/LaunchAgents/com.setapp.DesktopClient.*plist'
 trash $LOGGED_IN_USER '~/Library/Logs/Setapp'
+trash $LOGGED_IN_USER '~/Library/Preferences/com.setapp.DesktopClient.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.setapp.DesktopClient.SetappAgent.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.setapp.DesktopClient.savedState'

ee/maintained-apps/outputs/snagit/darwin.json

=== Install Script (no changes) ===
=== Uninstall // b52ff2b2 -> 59bfdeae ===

--- /tmp/old.8Yi93q	2026-07-22 20:49:11.031712607 +0000
+++ /tmp/new.TKhlfm	2026-07-22 20:49:11.031712607 +0000
@@ -5,6 +5,46 @@
 LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
 # functions
 
+quit_application() {
+  local bundle_id="$1"
+  local timeout_duration=10
+
+  # check if the application is running
+  local app_running
+  app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
+  if [[ "$app_running" != "true" ]]; then
+    return
+  fi
+
+  local console_user
+  console_user=$(stat -f "%Su" /dev/console)
+  if [[ -z "$console_user" || "$console_user" == "root" || "$console_user" == "loginwindow" ]]; then
+    echo "Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'."
+    return
+  fi
+
+  echo "Quitting application '$bundle_id'..."
+
+  # try to quit the application within the timeout period
+  local quit_success=false
+  SECONDS=0
+  while (( SECONDS < timeout_duration )); do
+    if osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1; then
+      if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then
+        echo "Application '$bundle_id' quit successfully."
+        quit_success=true
+        break
+      fi
+    fi
+    sleep 1
+  done
+
+  if [[ "$quit_success" = false ]]; then
+    echo "Application '$bundle_id' did not quit."
+  fi
+}
+
+
 trash() {
   local logged_in_user="$1"
   local target_file="$2"
@@ -52,9 +92,14 @@
   fi
 }
 
+quit_application 'com.TechSmith.Snagit'
 sudo rm -rf "$APPDIR/Snagit.app"
+trash $LOGGED_IN_USER '~/Library/Application Scripts/7TQL462TU8.com.techsmith.snagit'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.techsmith.snagit.sfl*'
+trash $LOGGED_IN_USER '~/Library/Application Support/Snagit'
 trash $LOGGED_IN_USER '~/Library/Caches/com.TechSmith.Snagit*'
 trash $LOGGED_IN_USER '~/Library/Group Containers/*.com.techsmith.snagit'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.TechSmith.Snagit*'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.TechSmith.Snagit*.plist'
 trash $LOGGED_IN_USER '~/Library/Preferences/com.techsmith.snagit.capturehelper*.plist'
 trash $LOGGED_IN_USER '~/Library/Saved Application State/com.TechSmith.Snagit*.savedState'

ee/maintained-apps/outputs/sourcetree/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/trezor-suite/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/typora/windows.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/visual-studio-code/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/vivaldi/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/wechat/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/whatsapp/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

ee/maintained-apps/outputs/workflowy/darwin.json

=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Updated 45 maintained-app manifests across macOS and Windows with new versions, patch-detection thresholds, installer URLs, and SHA-256 checksums. Existing install and uninstall references remain unchanged for most entries. PowerShell, Rider, Setapp, and Snagit received updated uninstall references or script contents, including revised product identifiers, version-specific paths, launchd cleanup, and expanded application data removal.

Possibly related PRs

  • fleetdm/fleet#49784: Updates the same maintained-app manifests and release metadata fields.
  • fleetdm/fleet#49776: Performs overlapping maintained-app version, patch query, URL, and checksum updates.
  • fleetdm/fleet#49743: Updates overlapping Advanced Installer and Akiflow manifest entries.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is far too brief and does not follow the required template sections or checklist items. Expand the PR description to include the required Related issue, checklist sections, Testing, and any applicable notes from the template.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: automated updates to Fleet-maintained app metadata.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fma-2607222041

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
ee/maintained-apps/outputs/microsoft-teams/darwin.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

ee/maintained-apps/outputs/microsoft-teams/windows.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

ee/maintained-apps/outputs/nosql-workbench/darwin.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

  • 41 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/maintained-apps/outputs/brave-browser/darwin.json`:
- Line 9: Update the Brave macOS manifest’s installer configuration around
installer_url to use a stable universal macOS artifact, or add an explicit
architecture/universal selector that prevents Intel Macs from receiving the
ARM-only Brave-Browser-arm64.dmg installer.

In `@ee/maintained-apps/outputs/powerphotos/darwin.json`:
- Around line 4-7: Replace the moving PowerPhotos artifact URL and no_check
configuration in ee/maintained-apps/outputs/powerphotos/darwin.json (lines 4-7)
with a version-specific immutable artifact reference and SHA-256 validation for
version 3.4.2. In ee/maintained-apps/outputs/trezor-suite/darwin.json (lines
4-12), remove reliance on the /latest/ path by ensuring it cannot resolve to a
newer artifact or by providing an immutable URL for version 26.7.2.

In `@ee/maintained-apps/outputs/setapp/darwin.json`:
- Line 20: Update plist_status inside remove_launchctl_service to query the same
launchctl domain being removed: use sudo launchctl list when should_sudo is true
and plain launchctl list otherwise. Keep the existing status check and removal
commands unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3f60d915-735d-49d5-8ea2-9fd099dae7f0

📥 Commits

Reviewing files that changed from the base of the PR and between 1a1b6e7 and 1a05353.

📒 Files selected for processing (44)
  • ee/maintained-apps/outputs/advanced-installer/windows.json
  • ee/maintained-apps/outputs/akiflow/darwin.json
  • ee/maintained-apps/outputs/beyond-compare/darwin.json
  • ee/maintained-apps/outputs/brave-browser/darwin.json
  • ee/maintained-apps/outputs/cleanmymac/darwin.json
  • ee/maintained-apps/outputs/clop/darwin.json
  • ee/maintained-apps/outputs/comet/windows.json
  • ee/maintained-apps/outputs/cursor/darwin.json
  • ee/maintained-apps/outputs/dataflare/darwin.json
  • ee/maintained-apps/outputs/dataflare/windows.json
  • ee/maintained-apps/outputs/firefox@developer-edition/darwin.json
  • ee/maintained-apps/outputs/firefox@nightly/darwin.json
  • ee/maintained-apps/outputs/granola/darwin.json
  • ee/maintained-apps/outputs/granola/windows.json
  • ee/maintained-apps/outputs/lookaway/darwin.json
  • ee/maintained-apps/outputs/loom/darwin.json
  • ee/maintained-apps/outputs/loom/windows.json
  • ee/maintained-apps/outputs/macwhisper/darwin.json
  • ee/maintained-apps/outputs/marsedit/darwin.json
  • ee/maintained-apps/outputs/megasync/windows.json
  • ee/maintained-apps/outputs/microsoft-teams/darwin.json
  • ee/maintained-apps/outputs/microsoft-teams/windows.json
  • ee/maintained-apps/outputs/nosql-workbench/darwin.json
  • ee/maintained-apps/outputs/nosql-workbench/windows.json
  • ee/maintained-apps/outputs/obs/darwin.json
  • ee/maintained-apps/outputs/only-switch/darwin.json
  • ee/maintained-apps/outputs/popclip/darwin.json
  • ee/maintained-apps/outputs/powerphotos/darwin.json
  • ee/maintained-apps/outputs/powershell/windows.json
  • ee/maintained-apps/outputs/prisma-browser/windows.json
  • ee/maintained-apps/outputs/pritunl/darwin.json
  • ee/maintained-apps/outputs/pritunl/windows.json
  • ee/maintained-apps/outputs/rider/darwin.json
  • ee/maintained-apps/outputs/rustrover/windows.json
  • ee/maintained-apps/outputs/setapp/darwin.json
  • ee/maintained-apps/outputs/snagit/darwin.json
  • ee/maintained-apps/outputs/sourcetree/darwin.json
  • ee/maintained-apps/outputs/trezor-suite/darwin.json
  • ee/maintained-apps/outputs/typora/windows.json
  • ee/maintained-apps/outputs/visual-studio-code/darwin.json
  • ee/maintained-apps/outputs/vivaldi/darwin.json
  • ee/maintained-apps/outputs/wechat/darwin.json
  • ee/maintained-apps/outputs/whatsapp/darwin.json
  • ee/maintained-apps/outputs/workflowy/darwin.json

"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.brave.Browser' AND version_compare(bundle_short_version, '150.1.92.143') < 0);"
},
"installer_url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable-arm64/192.141/Brave-Browser-arm64.dmg",
"installer_url": "https://updates-cdn.bravesoftware.com/sparkle/Brave-Browser/stable-arm64/192.143/Brave-Browser-arm64.dmg",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'brave-browser|arm64|x86_64|universal|architecture' \
  ee/maintained-apps server/mdm/maintainedapps

Repository: fleetdm/fleet

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== brave manifest =="
cat -n ee/maintained-apps/outputs/brave-browser/darwin.json

echo
echo "== files named darwin.json under brave-browser =="
fd -a '^darwin\.json$' ee/maintained-apps/outputs/brave-browser . 2>/dev/null || true

echo
echo "== architecture-related fields in maintained apps manifests =="
python3 - <<'PY'
import json, os
for root in ['ee/maintained-apps/outputs','server/mdm/maintainedapps']:
    if not os.path.isdir(root):
        continue
    for path in [os.path.join(root, f) for f in os.listdir(root) if f.endswith('.json')]:
        try:
            data=json.load(open(path, encoding='utf-8', errors='ignore'))
        except Exception:
            continue
        text=json.dumps(data, indent=2, ensure_ascii=False)
        if 'architecture' in text or 'arch' in text or 'arm64' in text or 'x86_64' in text or 'universal' in text:
            if os.path.basename(path) in ('darwin.json','windows.json'):
                print(f"\n--- {path} ---")
                key='architecture'
                def walk(obj,prefix=''):
                    if isinstance(obj, dict):
                        for k,v in obj.items():
                            p=f'{prefix}.{k}' if prefix else k
                            if k.lower() in ('architecture','arch','arches'):
                                print(f"{p}: {v}")
                            walk(v,p)
                    elif isinstance(obj, list):
                        for i,v in enumerate(obj):
                            walk(v,f'{prefix}[{i}]')
                walk(data)
PY

echo
echo "== references to maintained app app identifiers in maintainedapps code =="
rg -n "maintained.*app|app_identifier|installed_app|architecture|darwin\.json|Brave|brave" server/mdm/maintainedapps --glob '*.go' --glob '*.ts' --glob '*.json' | head -n 200

Repository: fleetdm/fleet

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== inspect likely maintained app generator/code around architectures =="
fd -a '^.*\.go$|package-summary\.yaml|maintained-apps' . | sed -n '1,200p' | rg 'server/mdm/maintainedapps|ee/maintained-apps|generated|generator|schema' || true

echo
echo "== read manifests around generated app names =="
for f in $(fd -a 'manifests?\.json$|package-summary\.yaml$' server/mdm/maintainedapps | sed -n '1,40p'); do
  echo "--- $f ---"
  sed -n '1,220p' "$f"
done

Repository: fleetdm/fleet

Length of output: 331


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== exact darwin manifests under brave-browser =="
find ee/maintained-apps/outputs/brave-browser -maxdiffdepth 3 -type f ! -path '*/.*' -name 'darwin.json' 2>/dev/null | sort -u

echo
echo "== package summaries containing brave =="
fd -a 'package-summary\.yaml$' server/mdm/maintainedapps ee/maintained-apps 2>/dev/null \
  | rg -n 'brave-browser|brave' || true

echo
echo "== architecture fields in server/mdm/maintainedapps manifests (focused) =="
python3 - <<'PY'
import json, os
root='server/mdm/maintainedapps'
for f in sorted(os.listdir(root)) if os.path.isdir(root) else []:
    p=os.path.join(root,f)
    if not os.path.isfile(p) or not f.endswith('.json'):
        continue
    try:
        data=json.load(open(p, encoding='utf-8'))
    except Exception:
        continue
    txt=json.dumps(data)
    if any(k in txt.lower() for k in ['architecture','arch','darwin.json','arm64','x86_64','universal','installed_app']):
        print(f'--- {p} ---')
        txt=json.dumps(data, indent=2)
        for i,line in enumerate(txt.splitlines(),1):
            if any(k in line.lower() for k in ['darwin.json','architecture','arch','arm64','x86_64','universal','installed_app']):
                print(f'{i}: {line}')
PY

echo
echo "== maintained app related symbols in server/mdm/maintainedapps source =="
rg -n 'architecture|arch|darw|installed_app|darw_in|selected_os|darwin\.json' server/mdm/maintainedapps --glob '*.go' --glob '*.yaml' --glob '*.json' | head -n 300

Repository: fleetdm/fleet

Length of output: 200


🏁 Script executed:

#!/bin/bash
set -u

echo "== tree entry for brave-browser outputs =="
find ee/maintained-apps/outputs/brave-browser -maxdepth 3 -type f 2>/dev/null | sort

echo
echo "== tree entry for server maintainedapps manifests =="
find server/mdm/maintainedapps -maxdepth 3 -type f 2>/dev/null | sort | sed -n '1,160p'

echo
echo "== git status/stat for relevant paths =="
git status --short -- ee/maintained-apps/outputs/brave-browser server/mdm/maintainedapps 2>/dev/null || true

echo
echo "== exact architecture/arch occurrences in maintained apps code =="
rg -n -i 'architecture|architecture'|rg 'maintainedapps|ee/maintained-apps' || true

Repository: fleetdm/fleet

Length of output: 4892


🏁 Script executed:

#!/bin/bash
set -u

echo "== exact darwin manifests under brave-browser =="
find ee/maintained-apps/outputs/brave-browser -maxdepth 3 -type f 2>/dev/null | sort

echo
echo "== package summaries referencing brave =="
find server/mdm/maintainedapps ee/maintained-apps -maxdepth 5 \( -name 'package-summary.yaml' -o -name '*.yaml' \) 2>/dev/null \
  | xargs -r rg -n 'brave-browser|brave' || true

echo
echo "== architecture fields in server/mdm/maintainedapps manifests =="
python3 - <<'PY'
import json, os
root='server/mdm/maintainedapps'
if not os.path.isdir(root):
    print('missing server/mdm/maintainedapps')
    raise SystemExit
for f in sorted(os.listdir(root)):
    p=os.path.join(root,f)
    if not os.path.isfile(p) or not f.endswith('.json'):
        continue
    try:
        data=json.load(open(p, encoding='utf-8'))
    except Exception:
        continue
    txt=json.dumps(data, indent=2)
    hits=[i for i,line in enumerate(txt.splitlines(),1) if any(k in line.lower() for k in ['architecture','darwin.json','arm64','x86_64','universal','installed_app'])]
    if not hits: 
        continue
    print(f'--- {p} ---')
    for i in hits:
        print(f'{i}: {txt.splitlines()[i-1]}')
PY

echo
echo "== maintained app related symbols in server/mdm/maintainedapps source =="
rg -n -i 'architecture|arch|darwin|installed_app|selected_os|x86|arm64|universal|os_arch' server/mdm/maintainedapps --glob '*.go' --glob '*.yaml' --glob '*.json' 2>/dev/null | sed -n '1,240p' || true

Repository: fleetdm/fleet

Length of output: 6367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== server maintained app schemas / definitions =="
rg -n -C 3 'type .*App|InstalledApp|InstallerURL|Platform|Architecture|PlatformOS|darwin\.json|Platforms|arches|Architecture' \
  server/mdm/maintainedapps/apps.json server/mdm/maintainedapps/testdata/brave-browser.json \
  server/mdm/maintainedapps/testdata/1password.json server/mdm/maintainedapps/testdata/visual-studio-code.json \
  --glob '*.json' | sed -n '1,260p'

echo
echo "== full brave manifest from server maintainedappstestdata =="
cat -n server/mdm/maintainedapps/testdata/brave-browser.json

echo
echo "== read-only structural parser for manifest architecture fields =="
python3 - <<'PY'
import json
from pathlib import Path
samples = [
    Path('server/mdm/maintainedapps/testdata/brave-browser.json'),
    Path('server/mdm/maintainedapps/testdata/1password.json'),
    Path('server/mdm/maintainedapps/testdata/google-chrome.json'),
    Path('server/mdm/maintainedapps/testdata/visual-studio-code.json'),
]
for path in samples:
    print(f'--- {path} ---')
    data=json.loads(path.read_text())
    if isinstance(data, list):
        data=data[0]
    def walk(o,pfx=''):
        if isinstance(o, dict):
            for k,v in o.items():
                if 'arch' in k.lower() or k in ('platform','platforms','packages','version'):
                    print(f'{pfx}.{k}: {json.dumps(v, ensure_ascii=False)[:300]}')
                walk(v,f'{pfx}.{k}')
    elif isinstance(o, list):
        for i,v in enumerate(o):
            walk(v,f'[{i}]')
    else:
        print(data)
    walk(data)
PY

Repository: fleetdm/fleet

Length of output: 201


🏁 Script executed:

#!/bin/bash
set -u

echo "== sync.go lines containing installer/platform fields =="
rg -n -C 4 'InstallerURL|Platform|InstalledApp|darwin\.json|Version|Manifest|Packages|Architecture' \
  server/mdm/maintainedapps/sync.go server/mdm/maintainedapps/installers.go \
  | sed -n '1,260p'

echo
echo "== inspect apps.json first entries =="
python3 - <<'PY'
import json
p='server/mdm/maintainedapps/apps.json'
data=json.load(open(p, encoding='utf-8'))
for app in data[:5]:
    if isinstance(app, list) and app: app=app[0]
    print('---', app.get('slug'), app.get('unique_identifier'), app.get('platform'), '---')
    for k in sorted(app.keys()):
        v=app[k]
        if isinstance(v, (dict,list)):
            print(k, 'type=', type(v).__name__, 'keys/len=', (list(v.keys()) if isinstance(v,dict) else len(v)))
        else:
            print(k, '=', v)

import subprocess
q='curl -s https://raw.githubusercontent.com/fleetdm/fleet/main/server/mdm/maintainedapps/sync.go'
subprocess.run(['sh','-c', f'tk=$([[ -x curl ]] && curl -s "{q}" || true); echo "${{tk::-1500}}" | sed -n ${{`#tk`}}p | wc -c && echo "$tk"'], text=True, cwd='/tmp')
PY

echo
echo "== current brave testdata URL exact field context =="
python3 - <<'PY'
import json
data=json.load(open('server/mdm/maintainedapps/testdata/brave-browser.json', encoding='utf-8'))
if isinstance(data, list): data=data[0]
d=data.get('versions', data).get('darwin', [])
if isinstance(d, list): d=list(d.values())[0] if d else {} else: d={}
print('darwin_url=', d.get('url'), 'darwin_arm64_url=', d.get('arm64', {}).get('url'), 'darwin_x86_64_url=', d.get('x86_64', {}).get('url'))
PY

Repository: fleetdm/fleet

Length of output: 9660


🏁 Script executed:

#!/bin/bash
set -u

echo "== sync.go cache and installed app definitions =="
rg -n -C 5 'InstallScript|InstallerURL|Platform\(\)|arm64|x86_64|darwin|Architecture' server/mdm/maintainedapps/sync.go server/mdm/maintainedapps/testdata/brave-browser.json apps.json 2>/dev/null || true

echo
echo "== precise text search for architecture discriminator in sync.go =="
rg -n -i 'architecture|x86_64|arm64|darwin\.json|platform\(\)|architecture|os_arch' server/mdm/maintainedapps/server/mdm/maintainedapps/sync.go 2>/dev/null | sed -n '1,180p' || true

echo
echo "== inspect installed app model field via package references =="
rg -n -C 4 'type MaintainedApp|InstallerURL\s+string|Platform\s+string|Architecture' . --glob '*.go' | sed -n '1,220p' || true

echo
echo "== read testdata brace line context correctly =="
python3 - <<'PY'
import json
data=json.load(open('server/mdm/maintainedapps/testdata/brave-browser.json', encoding='utf-8'))
if isinstance(data, list): data=data[0]
if 'versions' in data:
    selected=data['versions'][0] if data['versions'] else {}
    print('selected_url=', selected.get('installer_url'))
    for k in sorted(selected):
        print(k, type(selected[k]).__name__, ':', selected[k] if k not in {'queries','refs'} else '[...objects...]')
PY

Repository: fleetdm/fleet

Length of output: 18481


🌐 Web query:

Fleet managed apps maintain apps manifest File structure FMA apps platform darwin installer_url GitHub

💡 Result:

Fleet-maintained apps (FMA) use a standardized, versioned JSON manifest file structure to manage application metadata, installation scripts, and installer URLs [1][2]. These manifests are stored in the Fleet GitHub repository [1][3] and are consumed by the Fleet server to facilitate automated app management [2][4]. Manifest Structure Overview Each app is identified by a slug (e.g., box-drive/darwin) [5][6], and its data is defined in an individual JSON manifest file [1][2]. A central apps.json file tracks the available apps [1]. An individual app manifest file (e.g., for darwin platforms) typically contains the following core components [1][2]: - Versions: An array of objects, where each object represents a specific version of the app [1][2]. Each version entry includes: - version: The version string (e.g., 1.2.3.4) [1][2]. - installer_url: The direct download URL for the installer (e.g.,.dmg,.pkg, or.msi) [1][2]. - installer_sha256: The SHA256 checksum for verification [1][2]. - queries: A set of SQL queries, including an exists query used to check if the app is already installed on a host [1][2]. - script references: References (IDs) to install and uninstall scripts stored within the manifest's refs object [1][2]. - Refs: A map (dictionary) of script contents, where keys (IDs) map to the actual shell script content used for installation and uninstallation [1][2]. Data Lifecycle Fleet automates the maintenance of these manifests by ingesting data from sources like Homebrew Casks and WinGet manifests [7][4]. This process is managed via GitHub Actions workflows [7], which transform external metadata into the standardized Fleet JSON format before deploying the updates to the repository [1][7]. When the Fleet server requires information about an app—such as during an administrative add or API request—it fetches the relevant manifest file from the GitHub repository and hydrates the application object with the required installer URLs, scripts, and verification logic [2][4]. Users interacting with Fleet via GitOps do not manually manage these low-level JSON manifests; instead, they define the desired Fleet-maintained apps in their own configuration YAML files using the slug provided by Fleet [6][4].

Citations:


Use a stable universal macOS installer for Brave.

ee/maintained-apps/outputs/brave-browser/darwin.json is the only macOS manifest for this app, and it points to Brave-Browser-arm64.dmg. Add an explicit architecture/universal selector here, or use a universal artifact so Intel Macs in /maintained-apps/brave-browser/darwin don’t receive the ARM-only installer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/brave-browser/darwin.json` at line 9, Update the
Brave macOS manifest’s installer configuration around installer_url to use a
stable universal macOS artifact, or add an explicit architecture/universal
selector that prevents Intel Macs from receiving the ARM-only
Brave-Browser-arm64.dmg installer.

Comment on lines +4 to +7
"version": "3.4.2",
"queries": {
"exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.fatcatsoftware.PowerPhotos';",
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.fatcatsoftware.PowerPhotos' AND version_compare(bundle_short_version, '3.4.1') < 0);"
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.fatcatsoftware.PowerPhotos' AND version_compare(bundle_short_version, '3.4.2') < 0);"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | grep -E 'ee/maintained-apps/(outputs|.*\.json)$' | sed -n '1,120p'

echo
echo "== target manifests =="
for f in \
  ee/maintained-apps/outputs/powerphotos/darwin.json \
  ee/maintained-apps/outputs/trezor-suite/darwin.json
do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f" | sed -n '1,80p'
  else
    echo "missing $f"
  fi
done

echo
echo "== search InstallerURL / no_check / maintained-app hydration references =="
rg -n "InstallerURL|installer_url|no_check|maintained-app|maintained_apps|hydra|hydrat" -S . | sed -n '1,220p'

Repository: fleetdm/fleet

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ingest workflow =="
cat -n .github/workflows/ingest-maintained-apps.yml | sed -n '1,240p'

echo
echo "== maintained apps generator references =="
fd -i 'maintained|app|manifest|ingest' -t f ee 2>/dev/null | sed -n '1,120p'

echo
echo "== software_installer InstallerURL section =="
cat -n server/fleet/software_installer.go | sed -n '45,75p;56,68p;1170,1220p'

echo
echo "== orbit download section =="
cat -n orbit/pkg/installer/installer.go | sed -n '400,470p'

echo
echo "== target installer URL current existence/version indicators =="
for u in \
  "https://www.fatcatsoftware.com/powerphotos/PowerPhotos.zip" \
  "https://data.trezor.io/suite/releases/desktop/latest/Trezor-Suite-26.7.2-mac-arm64.dmg"
do
  echo "--- $u ---"
  curl -IsS --max-time 20 "$u" | sed -n '1,20p'
done

echo
echo "== check if PowerPhotos generic URL is a redirect/live and payload metadata =="
python3 - <<'PY'
import subprocess, tarfile, zipfile, zipfile
from urllib.request import urlopen
for url in [
    "https://www.fatcatsoftware.com/powerphotos/PowerPhotos.zip",
    "https://data.trezor.io/suite/releases/desktop/latest/Trezor-Suite-26.7.2-mac-arm64.dmg",
]:
    try:
        with urlopen(url, timeout=20) as r:
            print("URL", url, "final:", r.geturl(), "status:", r.status, "content_type:", r.headers.get("content-type"))
    except Exception as e:
        print("URL", url, "ERROR:", type(e).__name__, e)
PY

Repository: fleetdm/fleet

Length of output: 18371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== maintained-apps ingestion code outline =="
ast-grep outline ee/maintained-apps/main.go || true
ast-grep outline ee/maintained-apps/ingesters/homebrew/ingester.go || true

echo
echo "== key ingestion source snippets =="
for f in ee/maintained-apps/main.go ee/maintained-apps/ingesters/homebrew/ingester.go ee/server/service/maintained_apps.go
do
  [ -f "$f" ] || continue
  echo "--- $f ---"
  wc -l "$f"
  ast-grep outline "$f" | sed -n '1,220p'
done

echo
echo "== Search download validation/hash behavior in maintained app service =="
rg -n "InstallerURL|no_check|sha256|sha 256|checksum|hash|downloadSoftware|download software|download file|DownloadSoftwareInstallerFromURL|Content-Length|Content-Dispo|ApplicationName|BundleIdentifier|Version" ee/server/service/maintained_apps*.go -S -C 2

Repository: fleetdm/fleet

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== maintainers source relevant ranges =="
for f in ee/maintained-apps/ingesters/homebrew/ingester.go ee/server/service/maintained_apps_auto_update.go
do
  echo "--- $f ---"
  sed -n '126,280p' "$f"
done

echo
echo "== maintained apps helpers =="
fd -e go -t f . | xargs rg -n "func (.*DownloadInstaller|DownloadInstaller|noCheckHash|ExtractInstallerMetadata|versionMatches|MatchesVersion)" -S | sed -n '1,160p'

echo
echo "== command/maintained-apps package files =="
git ls-files | grep 'cmd/maintained-apps\|ee/maintained-apps' | sed -n '1,160p'

Repository: fleetdm/fleet

Length of output: 21083


Use immutable installer artifacts by hash across version-pinned manifests.

Fleet hydrates these manifests through the latest ingest flow, so a generic or moving URL can serve a different artifact for the pinned version, while no_check won’t catch it until the next ingestion changes the URL/hash.

  • ee/maintained-apps/outputs/powerphotos/darwin.json: replace the generic PowerPhotos.zip URL and no_check with a version-specific file plus SHA-256, or otherwise prove the artifact cannot be replaced.
  • ee/maintained-apps/outputs/trezor-suite/darwin.json: ensure the /latest/ path cannot swap a newer artifact or provide an immutable URL for 26.7.2.
📍 Affects 2 files
  • ee/maintained-apps/outputs/powerphotos/darwin.json#L4-L7 (this comment)
  • ee/maintained-apps/outputs/trezor-suite/darwin.json#L4-L12
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/powerphotos/darwin.json` around lines 4 - 7,
Replace the moving PowerPhotos artifact URL and no_check configuration in
ee/maintained-apps/outputs/powerphotos/darwin.json (lines 4-7) with a
version-specific immutable artifact reference and SHA-256 validation for version
3.4.2. In ee/maintained-apps/outputs/trezor-suite/darwin.json (lines 4-12),
remove reliance on the /latest/ path by ensuring it cannot resolve to a newer
artifact or by providing an immutable URL for version 26.7.2.

"refs": {
"1ba0fd31": "#!/bin/bash\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# extract contents\nunzip \"$INSTALLER_PATH\" -d \"$TMPDIR\"\n# copy to the applications folder\nquit_and_track_application 'com.setapp.DesktopClient.SetappAgent'\nif [ -d \"$APPDIR/Setapp.app\" ]; then\n\tsudo mv \"$APPDIR/Setapp.app\" \"$TMPDIR/Setapp.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Setapp.app\" \"$APPDIR\"\nrelaunch_application 'com.setapp.DesktopClient.SetappAgent'\n",
"e23bd29d": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\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\nsudo rm -rf \"$APPDIR/Setapp.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.setapp.DesktopClient.SetappAgent.FinderSyncExt'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.setapp.DesktopClient'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.setapp.DesktopClient.SetappAgent'\ntrash $LOGGED_IN_USER '~/Library/Logs/Setapp'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.setapp.DesktopClient.SetappAgent.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.setapp.DesktopClient.savedState'\n"
"9ef05eb6": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\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 # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.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 done\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\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\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.setapp.DesktopClient.SetappAgent'\nremove_launchctl_service 'com.setapp.DesktopClient.SetappAssistant'\nremove_launchctl_service 'com.setapp.DesktopClient.SetappLauncher'\nremove_launchctl_service 'com.setapp.DesktopClient.SetappUpdater'\nsudo rm -rf \"$APPDIR/Setapp.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.setapp.DesktopClient.SetappAgent.FinderSyncExt'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Setapp*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.setapp.DesktopClient'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.setapp.DesktopClient.SetappAgent'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.setapp.DesktopClient.SetappAgent.FinderSyncExt'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.setapp.DesktopClient*'\ntrash $LOGGED_IN_USER '~/Library/LaunchAgents/com.setapp.DesktopClient.*plist'\ntrash $LOGGED_IN_USER '~/Library/Logs/Setapp'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.setapp.DesktopClient.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.setapp.DesktopClient.SetappAgent.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.setapp.DesktopClient.savedState'\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Query the same launchctl domain that will be removed.

When should_sudo is true, plist_status is still populated with non-sudo launchctl list. A system-level Setapp job can therefore fail this check, skip sudo launchctl remove, and remain loaded after its plist is deleted. Use sudo launchctl list for the sudo branch and plain launchctl list otherwise.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/setapp/darwin.json` at line 20, Update
plist_status inside remove_launchctl_service to query the same launchctl domain
being removed: use sudo launchctl list when should_sudo is true and plain
launchctl list otherwise. Keep the existing status check and removal commands
unchanged.

@github-actions

Copy link
Copy Markdown
Contributor

Closing in favor of #49793.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants