#!/bin/zsh --no-rcs

# Pulse Uninstaller
# Removes Pulse and all related files
#

# --- configuration ---

appTitle="Pulse Endpoint"
appBundleIdentifier="com.qlabs.pulse"
appProcesses=("Pulse Endpoint")
appFiles=()
appLaunchAgents=()
appReceipts=("com.qlabs.pulse")
logLocation="/private/var/log/pulse-uninstaller.log"

# Application
appFiles+=("/Applications/Pulse Endpoint.app")
appFiles+=("/usr/local/bin/pulse")

# System data
appFiles+=("/Library/Application Support/Pulse Endpoint")

# User data
appFiles+=("<<Users>>/Library/Application Support/Pulse Endpoint")
appFiles+=("<<Users>>/Library/Logs/Pulse Endpoint")
appFiles+=("<<Users>>/Library/Preferences/com.qlabs.pulse.plist")
appFiles+=("<<Users>>/Library/Caches/com.qlabs.pulse")
appFiles+=("<<Users>>/Library/Saved Application State/com.qlabs.pulse.savedState")

# LaunchAgents (current label + legacy label for upgrades from older installs)
appLaunchAgents+=("/Library/LaunchAgents/com.qlabs.pulse.agent.plist")
appLaunchAgents+=("<<Users>>/Library/LaunchAgents/com.qlabs.pulse.agent.plist")
appLaunchAgents+=("/Library/LaunchAgents/com.qlabs.pulse.plist")
appLaunchAgents+=("<<Users>>/Library/LaunchAgents/com.qlabs.pulse.plist")

# --- functions ---

printlog() {
  timestamp=$(/bin/date +%F\ %T)
  if [ "$(whoami)" = "root" ]; then
    echo "$timestamp" "$1" | tee -a "$logLocation"
  else
    echo "$timestamp" "$1"
  fi
}

quitApp() {
  local process="$1"
  processStatus=$(/usr/bin/pgrep -x "$process")
  if [ "$processStatus" ]; then
    printlog "Stopping process: $process"
    /usr/bin/killall "$process" 2>/dev/null
    sleep 3
  else
    printlog "Process not running: $process"
  fi
}

removeFileDirectory() {
  local object="$1"
  # Refuse to delete through a symlink pointing at a directory. `rm -rf`
  # on a path whose final component is a symlink only removes the link
  # itself, but intermediate components can be symlinked too — e.g. a
  # malicious local user could point ~/Library/Application\ Support ->
  # /System, and this script runs as root. Treat any symlinked component
  # as untrusted and skip.
  if [ -L "$object" ]; then
    printlog "Refusing to remove symlink target: $object"
    /bin/rm -f "$object"
    return
  fi
  if [ -f "$object" ]; then
    printlog "Removing file: $object"
    /bin/rm -f "$object"
  elif [ -d "$object" ]; then
    printlog "Removing directory: $object"
    /bin/rm -Rf -- "$object"
  fi
}

removeLaunchAgent() {
  local object="$1"
  if [ -f "$object" ]; then
    local service_name=$(defaults read "$object" Label 2>/dev/null)
    local rootfolder=$(echo "$object" | awk -F/ '{print $2}')
    if [[ "$rootfolder" == "Users" ]]; then
      local user_name=$(echo "$object" | awk -F/ '{print $3}')
    else
      local user_name="$loggedInUser"
    fi
    local user_uid=$(/usr/bin/id -u "$user_name" 2>/dev/null)
    if [ -n "$user_uid" ] && launchctl print "gui/${user_uid}/${service_name}" &>/dev/null; then
      printlog "Unloading LaunchAgent: $object (user: $user_name)"
      launchctl bootout "gui/${user_uid}" "$object" &>/dev/null
    fi
    printlog "Removing LaunchAgent: $object"
    /bin/rm -f "$object"
  fi
}

forgetPackageReceipt() {
  local receipt="$1"
  if /usr/sbin/pkgutil --pkg-info "$receipt" &>/dev/null; then
    printlog "Forgetting package receipt: $receipt"
    /usr/sbin/pkgutil --forget "$receipt" &>/dev/null
  fi
}

# check for root
if [ "$(whoami)" != "root" ]; then
  echo "Error: this script must be run as root (sudo)"
  exit 1
fi

# get logged-in user
loggedInUser=$(/usr/sbin/scutil <<< "show State:/Users/ConsoleUser" | /usr/bin/awk '/Name :/ { print $3 }')

printlog "===== Pulse Endpoint Uninstaller ====="
printlog "Logged in user: $loggedInUser"

# quit the app

for process in "${appProcesses[@]}"; do
  quitApp "$process"
done

# remove launch agents

for userDir in /Users/*; do
  if [[ -d "$userDir" && "$userDir" != "/Users/Shared" && ! -L "$userDir" ]]; then
    userName=$(basename "$userDir")
    for agent in "${appLaunchAgents[@]}"; do
      resolvedAgent="${agent/<<Users>>/$userDir}"
      removeLaunchAgent "$resolvedAgent"
    done
  fi
done

# also handle non-user LaunchAgents (in /Library)
for agent in "${appLaunchAgents[@]}"; do
  if [[ "$agent" != *"<<Users>>"* ]]; then
    removeLaunchAgent "$agent"
  fi
done

# remove root helper daemon (SMAppService-registered LaunchDaemon).
# SMAppService places a symlink at /Library/LaunchDaemons/<label>.plist
# pointing into the app bundle; bootout unloads the running daemon, then
# we drop the symlink so launchd doesn't re-read it next boot.
helperLabel="com.qlabs.pulse.helper"
helperPlist="/Library/LaunchDaemons/${helperLabel}.plist"
if [ -L "$helperPlist" ] || [ -f "$helperPlist" ]; then
  printlog "Bootout helper daemon: system/$helperLabel"
  /bin/launchctl bootout "system/$helperLabel" &>/dev/null
  printlog "Removing helper daemon plist: $helperPlist"
  /bin/rm -f "$helperPlist"
fi

# remove files and directories

for userDir in /Users/*; do
  if [[ -d "$userDir" && "$userDir" != "/Users/Shared" && ! -L "$userDir" ]]; then
    userName=$(basename "$userDir")
    for file in "${appFiles[@]}"; do
      resolvedFile="${file/<<Users>>/$userDir}"
      removeFileDirectory "$resolvedFile"
    done
  fi
done

# also handle non-user files (in /Applications, /Library, /usr, etc.)
for file in "${appFiles[@]}"; do
  if [[ "$file" != *"<<Users>>"* ]]; then
    removeFileDirectory "$file"
  fi
done

# check for MDM managed preferences

if [ -f "/Library/Managed Preferences/${appBundleIdentifier}.plist" ]; then
  printlog "WARNING: MDM managed preferences found — not removing (managed by MDM)"
fi

# forget package receipts

for receipt in "${appReceipts[@]}"; do
  forgetPackageReceipt "$receipt"
done

# done

printlog "Pulse Endpoint uninstall complete"
exit 0
