From 62044bdb3f62c87cbcf444857c4eb9128cb0d245 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Mon, 17 Mar 2025 19:28:50 -0400 Subject: [PATCH] add pre and post install script fields to FMAv2 input schema (#27214) > No issue, follow up work for FMAv2 ingestion # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Ian Littman --- ee/maintained-apps/README.md | 32 +++++++++++++------ .../ingesters/homebrew/ingester.go | 14 ++++++-- .../inputs/homebrew/box-drive.json | 10 +++++- .../inputs/homebrew/cloudflare-warp.json | 5 ++- .../outputs/box-drive/darwin.json | 6 ++-- .../outputs/cloudflare-warp/darwin.json | 4 +-- 6 files changed, 53 insertions(+), 18 deletions(-) diff --git a/ee/maintained-apps/README.md b/ee/maintained-apps/README.md index bf9ded97b2..ab1f05a637 100644 --- a/ee/maintained-apps/README.md +++ b/ee/maintained-apps/README.md @@ -5,16 +5,24 @@ 1. Decide on a source for the app's metadata. We currently support homebrew as a source for macOS apps. 2. Find that app's metadata. For homebrew, you can visit https://formulae.brew.sh/ and find the app there. 3. Create a new file called `$YOUR_APP_NAME.json` in the `inputs/$SOURCE` directory. For - example, if you wanted to add Slack and use homebrew as the source, you would create the - file `inputs/homebrew/slack.json`. -4. Fill out the file according to the [breakdown below](#input-file-schema). For our example Slack app, it would look like this: + example, if you wanted to add Box Drive and use homebrew as the source, you would create the + file `inputs/homebrew/box-drive.json`. +4. Fill out the file according to the [breakdown below](#input-file-schema). For our example Box Drive app, it would look like this: ```json { - "name": "Slack", - "unique_identifier": "com.tinyspeck.slackmacgap", - "token": "slack", - "installer_format": "dmg", - "slug": "slack/darwin" + "name": "Box Drive", + "slug": "box-drive/darwin", + "unique_identifier": "com.box.desktop", + "token": "box-drive", + "installer_format": "pkg", + "pre_uninstall_scripts": [ + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER fileproviderctl domain remove -A com.box.desktop.boxfileprovider)", + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-archive-unsynced-content Box)", + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-preserve-unsynced-content Box)", + "(cd /Users/$LOGGED_IN_USER; defaults delete com.box.desktop)", + "echo \"${LOGGED_IN_USER} ALL = (root) NOPASSWD: /Library/Application\\ Support/Box/uninstall_box_drive_r\" >> /etc/sudoers.d/box_uninstall" + ], + "post_uninstall_scripts": ["rm /etc/sudoers.d/box_uninstall"] } ``` 5. Open a PR to the `fleet` repository with the new app file. This will trigger a CI job which will automatically update your PR with the required output files. These files contain important data such as the install and uninstall scripts for the app. @@ -51,4 +59,10 @@ For the app name part, use `-` to separate words if necessary, for example `adob The platform part can be any of these values: - `darwin` -For example, use a `slug` of `slack/darwin` for Slack on macOS. \ No newline at end of file +For example, use a `slug` of `box-drive/darwin` for Box Drive on macOS. + +#### `pre_uninstall_scripts` +These are command lines that will be run _before_ the generated uninstall script is executed. + +#### `post_uninstall_scripts` +These are command lines that will be run _after_ the generated uninstall script is executed. diff --git a/ee/maintained-apps/ingesters/homebrew/ingester.go b/ee/maintained-apps/ingesters/homebrew/ingester.go index 7432c62793..20a81c0fec 100644 --- a/ee/maintained-apps/ingesters/homebrew/ingester.go +++ b/ee/maintained-apps/ingesters/homebrew/ingester.go @@ -144,6 +144,14 @@ func (i *brewIngester) ingestOne(ctx context.Context, app inputApp) (*maintained out.SHA256 = cask.SHA256 out.Queries = maintained_apps.FMAQueries{Exists: fmt.Sprintf("SELECT 1 FROM apps WHERE bundle_identifier = '%s';", out.UniqueIdentifier)} out.Slug = app.Slug + if len(app.PreUninstallScripts) != 0 { + cask.PreUninstallScripts = app.PreUninstallScripts + } + + if len(app.PostUninstallScripts) != 0 { + cask.PostUninstallScripts = app.PostUninstallScripts + } + out.UninstallScript = uninstallScriptForApp(&cask) installScript, err := installScriptForApp(app, &cask) if err != nil { @@ -166,8 +174,10 @@ type inputApp struct { UniqueIdentifier string `json:"unique_identifier"` // InstallerFormat is the installer format used for installing this app. InstallerFormat string `json:"installer_format"` - - Slug string `json:"slug"` + // Slug is an identifier that combines the app's token and the target OS. + Slug string `json:"slug"` + PreUninstallScripts []string `json:"pre_uninstall_scripts"` + PostUninstallScripts []string `json:"post_uninstall_scripts"` } type brewCask struct { diff --git a/ee/maintained-apps/inputs/homebrew/box-drive.json b/ee/maintained-apps/inputs/homebrew/box-drive.json index 7eb4ca4f8a..2d25c5527e 100644 --- a/ee/maintained-apps/inputs/homebrew/box-drive.json +++ b/ee/maintained-apps/inputs/homebrew/box-drive.json @@ -3,5 +3,13 @@ "slug": "box-drive/darwin", "unique_identifier": "com.box.desktop", "token": "box-drive", - "installer_format": "pkg" + "installer_format": "pkg", + "pre_uninstall_scripts": [ + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER fileproviderctl domain remove -A com.box.desktop.boxfileprovider)", + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-archive-unsynced-content Box)", + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-preserve-unsynced-content Box)", + "(cd /Users/$LOGGED_IN_USER; defaults delete com.box.desktop)", + "echo \"${LOGGED_IN_USER} ALL = (root) NOPASSWD: /Library/Application\\ Support/Box/uninstall_box_drive_r\" >> /etc/sudoers.d/box_uninstall" + ], + "post_uninstall_scripts": ["rm /etc/sudoers.d/box_uninstall"] } diff --git a/ee/maintained-apps/inputs/homebrew/cloudflare-warp.json b/ee/maintained-apps/inputs/homebrew/cloudflare-warp.json index 11abfa5a40..826099e43c 100644 --- a/ee/maintained-apps/inputs/homebrew/cloudflare-warp.json +++ b/ee/maintained-apps/inputs/homebrew/cloudflare-warp.json @@ -3,5 +3,8 @@ "slug": "cloudflare-warp/darwin", "unique_identifier": "com.cloudflare.1dot1dot1dot1.macos", "token": "cloudflare-warp", - "installer_format": "pkg" + "installer_format": "pkg", + "post_uninstall_scripts": [ + "/Applications/Cloudflare\\ WARP.app/Contents/Resources/uninstall.sh" + ] } diff --git a/ee/maintained-apps/outputs/box-drive/darwin.json b/ee/maintained-apps/outputs/box-drive/darwin.json index b50fdd9aa9..d80e483c27 100644 --- a/ee/maintained-apps/outputs/box-drive/darwin.json +++ b/ee/maintained-apps/outputs/box-drive/darwin.json @@ -8,12 +8,12 @@ "installer_url": "https://e3.boxcdn.net/desktop/releases/mac/BoxDrive-2.43.205.pkg", "unique_identifier": "com.box.desktop", "install_script_ref": "31e1a311", - "uninstall_script_ref": "14b3598b", + "uninstall_script_ref": "d9e98295", "sha256": "200c5fa6d8eec5516924938d46e7fa6122fdc189044fd935e8c155faa5beaf3d" } ], "refs": { - "14b3598b": "#!/bin/sh\n\n# variables\nLOGGED_IN_USER=$(scutil \u003c\u003c\u003c \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n if ! osascript -e \"application id \\\"$bundle_id\\\" is running\" 2\u003e/dev/null; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 \u0026\u0026 \"$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 \u003c timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" \u003e/dev/null 2\u003e\u00261; then\n if ! pgrep -f \"$bundle_id\" \u003e/dev/null 2\u003e\u00261; 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\u003e/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\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.box.desktop.helper'\nquit_application 'com.box.Box-Local-Com-Server'\nquit_application 'com.box.desktop'\nquit_application 'com.box.desktop.findersyncext'\nquit_application 'com.box.desktop.helper'\nquit_application 'com.box.desktop.ui'\n(cd /Users/$LOGGED_IN_USER \u0026\u0026 sudo -u \"$LOGGED_IN_USER\" '/Library/Application Support/Box/uninstall_box_drive')\nsudo pkgutil --forget 'com.box.desktop.installer.*'\ntrash $LOGGED_IN_USER '~/.Box_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Application Support/FileProvider/com.box.desktop.boxfileprovider'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.box.desktop.findersyncext'\ntrash $LOGGED_IN_USER '~/Library/Logs/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.ui.plist'\n", - "31e1a311": "#!/bin/sh\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath $INSTALLER_PATH)\")\n\n# install pkg files\nsudo installer -pkg \"$TMPDIR/BoxDrive-2.43.205.pkg\" -target /\n" + "31e1a311": "#!/bin/sh\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath $INSTALLER_PATH)\")\n\n# install pkg files\nsudo installer -pkg \"$TMPDIR/BoxDrive-2.43.205.pkg\" -target /\n", + "d9e98295": "#!/bin/sh\n\n# variables\nLOGGED_IN_USER=$(scutil \u003c\u003c\u003c \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n if ! osascript -e \"application id \\\"$bundle_id\\\" is running\" 2\u003e/dev/null; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 \u0026\u0026 \"$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 \u003c timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" \u003e/dev/null 2\u003e\u00261; then\n if ! pgrep -f \"$bundle_id\" \u003e/dev/null 2\u003e\u00261; 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\u003e/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\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\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER fileproviderctl domain remove -A com.box.desktop.boxfileprovider)\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-archive-unsynced-content Box)\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-preserve-unsynced-content Box)\n(cd /Users/$LOGGED_IN_USER; defaults delete com.box.desktop)\necho \"${LOGGED_IN_USER} ALL = (root) NOPASSWD: /Library/Application\\ Support/Box/uninstall_box_drive_r\" \u003e\u003e /etc/sudoers.d/box_uninstall\nremove_launchctl_service 'com.box.desktop.helper'\nquit_application 'com.box.Box-Local-Com-Server'\nquit_application 'com.box.desktop'\nquit_application 'com.box.desktop.findersyncext'\nquit_application 'com.box.desktop.helper'\nquit_application 'com.box.desktop.ui'\n(cd /Users/$LOGGED_IN_USER \u0026\u0026 sudo -u \"$LOGGED_IN_USER\" '/Library/Application Support/Box/uninstall_box_drive')\nsudo pkgutil --forget 'com.box.desktop.installer.*'\nrm /etc/sudoers.d/box_uninstall\ntrash $LOGGED_IN_USER '~/.Box_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Application Support/FileProvider/com.box.desktop.boxfileprovider'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.box.desktop.findersyncext'\ntrash $LOGGED_IN_USER '~/Library/Logs/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.ui.plist'\n" } } \ No newline at end of file diff --git a/ee/maintained-apps/outputs/cloudflare-warp/darwin.json b/ee/maintained-apps/outputs/cloudflare-warp/darwin.json index 99a9ea78a3..ed1cb4a971 100644 --- a/ee/maintained-apps/outputs/cloudflare-warp/darwin.json +++ b/ee/maintained-apps/outputs/cloudflare-warp/darwin.json @@ -8,12 +8,12 @@ "installer_url": "https://downloads.cloudflareclient.com/v1/download/macos/version/2025.1.861.0", "unique_identifier": "com.cloudflare.1dot1dot1dot1.macos", "install_script_ref": "efb6e303", - "uninstall_script_ref": "2be21918", + "uninstall_script_ref": "b75a2692", "sha256": "6ecdd09f2cb0b36f16be8272bf77db487dfbce98243b5445491b6fd96e35914d" } ], "refs": { - "2be21918": "#!/bin/sh\n\n# variables\nLOGGED_IN_USER=$(scutil \u003c\u003c\u003c \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n if ! osascript -e \"application id \\\"$bundle_id\\\" is running\" 2\u003e/dev/null; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 \u0026\u0026 \"$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 \u003c timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" \u003e/dev/null 2\u003e\u00261; then\n if ! pgrep -f \"$bundle_id\" \u003e/dev/null 2\u003e\u00261; 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\u003e/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\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.cloudflare.1dot1dot1dot1.macos.loginlauncherapp'\nremove_launchctl_service 'com.cloudflare.1dot1dot1dot1.macos.warp.daemon'\nquit_application 'com.cloudflare.1dot1dot1dot1.macos'\n(cd /Users/$LOGGED_IN_USER \u0026\u0026 sudo -u \"$LOGGED_IN_USER\" '/Applications/Cloudflare WARP.app/Contents/Resources/uninstall.sh')\n(cd /Users/$LOGGED_IN_USER \u0026\u0026 sudo -u \"$LOGGED_IN_USER\" '%!s(bool=true)')\nsudo pkgutil --forget 'com.cloudflare.1dot1dot1dot1.macos'\nsudo rm -rf '/usr/local/bin/warp-cli'\nsudo rm -rf '/usr/local/bin/warp-dex'\nsudo rm -rf '/usr/local/bin/warp-diag'\ntrash $LOGGED_IN_USER '/Library/LaunchDaemons/com.cloudflare.1dot1dot1dot1.macos.warp.daemon.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.cloudflare.1dot1dot1dot1.macos'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.cloudflare.1dot1dot1dot1.macos'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.cloudflare.1dot1dot1dot1.macos'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.cloudflare.1dot1dot1dot1.macos'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.cloudflare.1dot1dot1dot1.macos.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.cloudflare.1dot1dot1dot1.macos.plist'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.cloudflare.1dot1dot1dot1.macos'\n", + "b75a2692": "#!/bin/sh\n\n# variables\nLOGGED_IN_USER=$(scutil \u003c\u003c\u003c \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n if ! osascript -e \"application id \\\"$bundle_id\\\" is running\" 2\u003e/dev/null; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 \u0026\u0026 \"$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 \u003c timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" \u003e/dev/null 2\u003e\u00261; then\n if ! pgrep -f \"$bundle_id\" \u003e/dev/null 2\u003e\u00261; 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\u003e/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\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.cloudflare.1dot1dot1dot1.macos.loginlauncherapp'\nremove_launchctl_service 'com.cloudflare.1dot1dot1dot1.macos.warp.daemon'\nquit_application 'com.cloudflare.1dot1dot1dot1.macos'\n(cd /Users/$LOGGED_IN_USER \u0026\u0026 sudo -u \"$LOGGED_IN_USER\" '%!s(bool=true)')\n(cd /Users/$LOGGED_IN_USER \u0026\u0026 sudo -u \"$LOGGED_IN_USER\" '/Applications/Cloudflare WARP.app/Contents/Resources/uninstall.sh')\nsudo pkgutil --forget 'com.cloudflare.1dot1dot1dot1.macos'\nsudo rm -rf '/usr/local/bin/warp-cli'\nsudo rm -rf '/usr/local/bin/warp-dex'\nsudo rm -rf '/usr/local/bin/warp-diag'\n/Applications/Cloudflare\\ WARP.app/Contents/Resources/uninstall.sh\ntrash $LOGGED_IN_USER '/Library/LaunchDaemons/com.cloudflare.1dot1dot1dot1.macos.warp.daemon.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.cloudflare.1dot1dot1dot1.macos'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.cloudflare.1dot1dot1dot1.macos'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.plausiblelabs.crashreporter.data/com.cloudflare.1dot1dot1dot1.macos'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.cloudflare.1dot1dot1dot1.macos.loginlauncherapp'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.cloudflare.1dot1dot1dot1.macos'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.cloudflare.1dot1dot1dot1.macos.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.cloudflare.1dot1dot1dot1.macos.plist'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.cloudflare.1dot1dot1dot1.macos'\n", "efb6e303": "#!/bin/sh\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath $INSTALLER_PATH)\")\n\n# install pkg files\nsudo installer -pkg \"$TMPDIR/Cloudflare_WARP_2025.1.861.0.pkg\" -target /\n" } } \ No newline at end of file