diff --git a/macOS-Auto‐Start-Service-Setup-(for-Remote---MDM-Deployment).md b/macOS-Auto‐Start-Service-Setup-(for-Remote---MDM-Deployment).md
new file mode 100644
index 0000000..d2f53f2
--- /dev/null
+++ b/macOS-Auto‐Start-Service-Setup-(for-Remote---MDM-Deployment).md
@@ -0,0 +1,572 @@
+# macOS Auto-Start Service Setup (for Remote / MDM Deployment)
+
+This guide explains how to use `install_service.sh` to set up RustDesk (or a Custom Client) as an auto-start service on macOS via the command line, for scenarios where the GUI "Install" button is not available.
+
+install_service.sh
+
+```bash
+#!/bin/bash
+#
+# rustdesk-daemon-install - public domain 2024
+
+# Ver Who Date Note
+# 001 GK 241219 Initial setup - tested on ProxMox machine - permissions had to be manually responded to in GUI
+# 002 HM 250224 Fix custom client support: APP_NAME, pref paths, plist labels, MDM user detection
+# 003 HM 250224 Fix GetAssets timing (require $APP), apply correct_app_name to install.scpt
+# 004 HM 250224 Split GetAssets: view options (-v) no longer blocked by user/app checks
+# 005 HM 250224 Fix -a/-d/-s blocked by user validation; fix dscl space-in-path truncation
+
+
+# Setup RustDesk services for Mac - even in a VM environment (presuming a copy of Preferences from another machine)
+
+# Optionally set ID and password - to make it work on a MacOS Vm
+
+# Server is for a user
+# Service is for the machine
+
+#
+# This script will find the app in /Applications or a subfolder or subfolder of a subfolder
+#
+
+# From lib/log
+Msg() { echo "$(date +%d:%k%M%S) $$ $@" >&2; }
+Err() { Msg "<***> $@ <***>"; return 42; }
+Throw() { [ -n "$1" ] && Msg "=== $1 ==="; exit ${2:-22}; exit 9; }
+
+#
+# ===== CUSTOM CLIENT CONFIGURATION =====
+# For custom clients, only change DEFAULTPATH to your app path.
+# Everything else is auto-derived.
+#
+# Example:
+# DEFAULTPATH='/Applications/MyApp.app'
+# DEFAULTPATH='/Applications/Subfolder/MyApp.app'
+# DEFAULTPATH='/opt/MyApp.app' (also works — FindApp checks DEFAULTPATH first)
+#
+# How naming works (hardcoded in RustDesk, cannot be changed):
+# APP_NAME = basename of DEFAULTPATH without .app (e.g. "MyApp")
+# FULL_NAME = "com.carriez.{APP_NAME}" (e.g. "com.carriez.MyApp")
+# Plist files = /Library/LaunchDaemons/com.carriez.MyApp_service.plist
+# /Library/LaunchAgents/com.carriez.MyApp_server.plist
+# Config path = ~/Library/Preferences/com.carriez.MyApp/
+# Config files = MyApp.toml, MyApp2.toml
+#
+# Note: The app's bundle identifier (e.g. com.mycompany.myapp) does NOT affect
+# any of the above. RustDesk always uses "com.carriez.{APP_NAME}" internally.
+# ========================================
+#
+
+# ========================== CONFIGURATION ==========================
+
+DEFAULTPATH='/Applications/RustDesk.app'
+# On reinstall, clear the "stop-service" flag left by a previous uninstall:
+# 1 = remove stop-service line from existing toml configs before installing
+# (recommended for MDM redeployment — prevents the "service running but not
+# connecting" issue when redeploying to a Mac that had the service stopped/uninstalled)
+# 0 = leave existing config untouched (original behavior)
+CLEAR_STOP_SERVICE=1
+# If config files don't exist:
+# 1 = auto-create empty config files (recommended for custom clients / MDM deployment,
+# server config is embedded in the binary at build time, service will populate on first run)
+# 0 = exit with error (original behavior, useful when you need pre-configured toml files)
+AUTO_CREATE_CONFIG=1
+
+# ========================== END CONFIGURATION ==========================
+
+# Derive APP_NAME from DEFAULTPATH (e.g. /Applications/Test2.app -> Test2)
+APP_NAME="$(basename "$DEFAULTPATH" .app)"
+
+# FULL_NAME matches RustDesk's get_full_name(): "{ORG}.{APP_NAME}"
+# ORG is always "com.carriez" on macOS (hardcoded in config.rs)
+FULL_NAME="com.carriez.${APP_NAME}"
+
+# Are we running as root - not really normal for a mac - maybe sudo'd
+# Enhanced for MDM context where SUDO_USER and USER may be empty
+FORUSER_OVERRIDE=''
+
+if [ $(id -u) -eq 0 ]; then
+ SUDO=''
+ FORUSER=${SUDO_USER:-$USER}
+ # In MDM postinstall context, both SUDO_USER and USER can be empty
+ if [ -z "$FORUSER" ] || [ "$FORUSER" = "root" ]; then
+ FORUSER=$(scutil <<< "show State:/Users/ConsoleUser" 2>/dev/null | awk '/Name :/ && !/loginwindow/ { print $3 }')
+ fi
+else
+ SUDO='sudo'
+ FORUSER=$(id -un)
+fi
+
+APP=''
+
+AGENT='' # Assets from Github
+DAEMON=''
+ASCPT=''
+
+USESCRIPT=0 # Use AppleScript instead of direct install
+USE_ID='' # Set ID of this machine via id= in toml file
+USE_PWD='' # Set permanent password to this if non-null
+VIEW_ONLY=0 # Set by -a/-d/-s to skip the infinite wait loop
+
+agent_path="/Library/LaunchAgents/${FULL_NAME}_server.plist"
+daemon_path="/Library/LaunchDaemons/${FULL_NAME}_service.plist"
+root_pref_path="/var/root/Library/Preferences/$FULL_NAME"
+# user_pref_path is set after FORUSER is resolved (depends on user's home directory)
+
+pref_files="${APP_NAME}.toml ${APP_NAME}2.toml"
+
+EnsureConfig()
+ {
+ local i
+ Msg "Verify $FORUSER ($FORUID) preferences..."
+
+ # Ensure preference directory and config files exist
+ if [ ! -d "$user_pref_path" ]; then
+ if [ "$AUTO_CREATE_CONFIG" -eq 1 ]; then
+ Msg "Creating preference directory: $user_pref_path"
+ $SUDO mkdir -p "$user_pref_path"
+ $SUDO chown "$FORUSER" "$user_pref_path"
+ else
+ Throw "Preference directory $user_pref_path does not exist for user $FORUSER"
+ fi
+ fi
+ for i in $pref_files; do
+ if [ ! -s "$user_pref_path/$i" ]; then
+ if [ "$AUTO_CREATE_CONFIG" -eq 1 ]; then
+ Msg "Config file $i not found for user $FORUSER — creating empty config (service will populate on first run)"
+ $SUDO touch "$user_pref_path/$i"
+ $SUDO chown "$FORUSER" "$user_pref_path/$i"
+ else
+ Throw "Missing preference file $i for user $FORUSER - this is needed for the system-wide settings copy
+
+ If running this in a VM
+ Copy a known good $FULL_NAME
+ from ~/Library/Preferences
+ on a real mac
+ (or a Linux .config/$FULL_NAME)
+ to $user_pref_path on this VM
+
+ Then run this script again and use the -i ID option to
+ set the ID of this ${APP_NAME} node.
+
+ Or set AUTO_CREATE_CONFIG=1 to auto-create empty config files.
+"
+ fi
+ fi
+ done
+ }
+
+DownloadAssets()
+ {
+ APP_NAME_LOWER=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')
+
+ # Read real bundle identifier from the app's Info.plist (matches macos.rs get_bundle_id())
+ BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$APP/Contents/Info.plist" 2>/dev/null)
+ if [ -z "$BUNDLE_ID" ]; then
+ Msg "Warning: Cannot read CFBundleIdentifier from $APP/Contents/Info.plist — using com.carriez.${APP_NAME_LOWER} as fallback"
+ BUNDLE_ID="com.carriez.${APP_NAME_LOWER}"
+ fi
+
+ Msg 'Loading assets from github'
+ # Download plist templates and apply replacements to match correct_app_name() in macos.rs:
+ # 1. Replace AssociatedBundleIdentifiers (com.carriez.rustdesk → real bundle ID)
+ # 2. Replace lowercase "rustdesk" → lowercase app name (for log paths etc.)
+ # 3. Replace "RustDesk" → APP_NAME (for labels, executable name, app path)
+ AGENT=$(curl -sf 'https://raw.githubusercontent.com/rustdesk/rustdesk/master/src/platform/privileges_scripts/agent.plist' \
+ | sed -e "s|com.carriez.rustdesk|${BUNDLE_ID}|g" \
+ -e "s|rustdesk|${APP_NAME_LOWER}|g" \
+ -e "s|RustDesk|${APP_NAME}|g" \
+ -e "s|/Applications/${APP_NAME}.app|$APP|g")
+ DAEMON=$(curl -sf 'https://raw.githubusercontent.com/rustdesk/rustdesk/master/src/platform/privileges_scripts/daemon.plist' \
+ | sed -e "s|com.carriez.rustdesk|${BUNDLE_ID}|g" \
+ -e "s|rustdesk|${APP_NAME_LOWER}|g" \
+ -e "s|RustDesk|${APP_NAME}|g" \
+ -e "s|/Applications/${APP_NAME}.app|$APP|g")
+ # Apply same correct_app_name() replacements to install.scpt (matches macos.rs:200)
+ ASCPT=$(curl -sf 'https://raw.githubusercontent.com/rustdesk/rustdesk/master/src/platform/privileges_scripts/install.scpt' \
+ | sed -e "s|com.carriez.rustdesk|${BUNDLE_ID}|g" \
+ -e "s|rustdesk|${APP_NAME_LOWER}|g" \
+ -e "s|RustDesk|${APP_NAME}|g")
+ [ -z "$AGENT" -o -z "$DAEMON" -o -z "$ASCPT" ] && Throw "Error getting one or more assets - url probably changed. (A/D/S sizes: ${#AGENT}/${#DAEMON}/${#ASCPT})"
+ }
+
+InstallViaAppleScript()
+ {
+ Msg "Current User: $FORUSER"
+
+ # This script is pretty stoopid. It does not get the home directory e.g. by algorithm
+ $SUDO osascript -e "$ASCPT" "$DAEMON" "$AGENT" "$FORUSER"
+ }
+
+KickstartServerGUI()
+ {
+ if [ -s "$agent_path" ]; then
+ Msg "Kickstart server for $FORUID ($FORUSER)"
+ $SUDO launchctl enable "gui/$FORUID/${FULL_NAME}_server" 2>/dev/null
+ # kickstart -k = kill existing, -p = print PID
+ if ! $SUDO launchctl kickstart -kp "gui/$FORUID/${FULL_NAME}_server" 2>/dev/null; then
+ Msg "Kickstart skipped — GUI session for $FORUSER not available (expected in MDM context). Agent will auto-start on user login."
+ fi
+ else
+ Err "Ooops - where is $agent_path"
+ fi
+ }
+
+InstallViaShell()
+ {
+ Msg "Install agent: $agent_path"
+ echo "$AGENT" | $SUDO tee "$agent_path" >/dev/null
+
+ Msg "Install daemon: $daemon_path"
+ echo "$DAEMON" | $SUDO tee "$daemon_path" >/dev/null
+
+ # Should already be - but incase they were created by user error
+ $SUDO chown 0:0 "$agent_path" "$daemon_path"
+
+ # Clear stale stop-service flag from previous uninstall (prevents service from
+ # ignoring rendezvous connections even though launchd started it successfully).
+ # The flag is written by RustDesk's uninstall_service() into {APP_NAME}2.toml
+ # (Config2 options store). Controlled by CLEAR_STOP_SERVICE option.
+ if [ "$CLEAR_STOP_SERVICE" -eq 1 ]; then
+ local opts_toml="${APP_NAME}2.toml"
+ for _cfg in "$user_pref_path/$opts_toml" "$root_pref_path/$opts_toml"; do
+ if [ -f "$_cfg" ] && $SUDO grep -qE '^[[:space:]]*"?stop-service"?[[:space:]]*=' "$_cfg" 2>/dev/null; then
+ Msg "Clearing stale stop-service flag in $_cfg"
+ $SUDO sed -i'.bak' '/^[[:space:]]*"*stop-service"*[[:space:]]*=/d' "$_cfg"
+ $SUDO rm -f "$_cfg.bak"
+ fi
+ done
+ fi
+
+ local i
+ $SUDO mkdir -p "$root_pref_path"
+ for i in $pref_files; do
+ $SUDO cp -a "$user_pref_path/$i" "$root_pref_path"
+ done
+
+ # Use modern launchctl API (bootstrap/bootout) instead of deprecated load/remove
+ # Daemon = system-wide (system domain), Agent = per-user (gui/ domain)
+
+ Msg "Unload / Reload Daemon (system domain)"
+ # Ensure clean state: disable + bootout (handles cases where service is stuck)
+ $SUDO launchctl disable "system/${FULL_NAME}_service" 2>/dev/null
+ $SUDO launchctl bootout "system/${FULL_NAME}_service" 2>/dev/null
+ $SUDO launchctl bootout system "$daemon_path" 2>/dev/null
+ # Also try legacy unload in case bootstrap API state is inconsistent
+ $SUDO launchctl unload -w "$daemon_path" 2>/dev/null
+ sleep 1
+ if ! $SUDO launchctl bootstrap system "$daemon_path" 2>/dev/null; then
+ Msg "bootstrap failed — falling back to launchctl load -w"
+ if ! $SUDO launchctl load -w "$daemon_path"; then
+ Err "Daemon load also failed. Checking /tmp/${APP_NAME_LOWER}_service.err for details."
+ $SUDO cat "/tmp/${APP_NAME_LOWER}_service.err" 2>/dev/null
+ Throw "Failed to load daemon ${FULL_NAME}_service"
+ fi
+ fi
+ $SUDO launchctl enable "system/${FULL_NAME}_service" 2>/dev/null
+
+ Msg "Unload / Reload Agent for $FORUSER (gui/$FORUID domain)"
+ # Agent must be loaded into the user's GUI domain, not the system domain.
+ # In MDM postinstall context, the GUI domain may not exist yet (user not logged in).
+ # In that case, bootstrap will fail — this is expected. launchd will automatically
+ # load the agent from /Library/LaunchAgents/ when the user logs in.
+ $SUDO launchctl bootout "gui/$FORUID/${FULL_NAME}_server" >/dev/null 2>&1
+ if $SUDO launchctl bootstrap "gui/$FORUID" "$agent_path" 2>/dev/null; then
+ $SUDO launchctl enable "gui/$FORUID/${FULL_NAME}_server"
+ else
+ Msg "Agent bootstrap skipped — GUI session for $FORUSER not available (expected in MDM context). Agent will auto-load on user login."
+ fi
+ }
+
+FindApp()
+ {
+ # First check DEFAULTPATH directly (supports custom paths outside /Applications)
+ if [ -d "$DEFAULTPATH" ]; then
+ APP="$DEFAULTPATH"
+ Msg "Found app at DEFAULTPATH: $APP"
+ return
+ fi
+ # Fall back: scan /Applications up to two levels deep
+ local d
+ APP=''
+ for d in /Applications /Applications/* /Applications/*/*; do if [ -d "$d" ]; then
+ [ -d "$d/${APP_NAME}.app" ] && { APP="$d/${APP_NAME}.app"; Msg "Found app: $APP"; break; }
+ fi; done
+ }
+
+IsServiceLoaded()
+ {
+ local p
+ p=$($SUDO launchctl list 2>/dev/null | while read pid sta com; do
+ [ "$com" = "$1" ] && { echo "$pid:$sta"; break; }
+ done)
+ [ -n "$p" ]
+ }
+
+Help()
+ {
+ Throw "
+
+Usage: ${0##*/} [options]
+
+ -a Show Agent plist contents
+ -d Show Daemon plist contents
+ -h This help
+ -i id Set ${APP_NAME} ID (does not install service)
+ -p pwd Set ${APP_NAME} permanent password (does not install service)
+ -s Show Script contents
+ -S Use AppleScript to install - not recommended
+ -u user Specify target user (for MDM/headless context)
+ -v List the Agent/Daemon plists installed
+"
+ }
+
+
+# Begin Script Body
+Msg "##################################################################"
+Msg "# $(date) | Starting ${0##*/}"
+Msg "#* *#"
+Msg "#** If prompted for a password - that is proably sudo **#"
+Msg "##################################################################"
+
+# === Phase 1: Parse all options ===
+while getopts 'adhi:p:sSvu:' o; do case "$o" in
+ 'a'|'d'|'s') VIEW_ONLY=1 ;;
+ 'h') Help ;;
+ 'i') USE_ID="$OPTARG" ;;
+ 'p') USE_PWD="$OPTARG" ;;
+ 'u') FORUSER_OVERRIDE="$OPTARG" ;;
+ 'v') ls -l "$agent_path" "$daemon_path" 2>/dev/null; Throw 'Done.' ;;
+ 'S') USESCRIPT=1 ;;
+ *) ;;
+esac; done
+OPTIND=1 # Reset for second pass
+
+# === Phase 1.5: Handle -i/-p (set ID/password only, no service install) ===
+if [ -n "$USE_ID" ] || [ -n "$USE_PWD" ]; then
+ FindApp
+ while [ ! -d "$APP" ]; do
+ Msg "Could not find ${APP_NAME}.app installed in /Applications/... - sleeping 30"
+ sleep 30
+ FindApp
+ done
+ local_bin="${APP}/Contents/MacOS/${APP_NAME}"
+ if [ -n "$USE_ID" ]; then
+ Msg "Setting ID to: $USE_ID"
+ $SUDO "$local_bin" --set-id "$USE_ID"
+ fi
+ if [ -n "$USE_PWD" ]; then
+ Msg "Setting permanent password"
+ $SUDO "$local_bin" --password "$USE_PWD"
+ fi
+ Throw "Done." 0
+fi
+
+# === Phase 2: Find app + download assets (needed by -a/-d/-s view options) ===
+FindApp
+
+# Wait for app to be installed (e.g. MDM deployment in progress)
+# For view-only options (-a/-d/-s), don't loop — fail fast
+while [ ! -d "$APP" ]; do
+ if [ "$VIEW_ONLY" -eq 1 ]; then
+ Throw "Cannot find ${APP_NAME}.app in /Applications — required to generate plist/script content"
+ fi
+ Msg "Could not find ${APP_NAME}.app installed in /Applications/... - sleeping 30"
+ sleep 30
+ FindApp
+done
+
+Msg "##################################################################"
+# DownloadAssets requires $APP to be set (reads Info.plist, substitutes app path in plists)
+DownloadAssets
+
+# === Phase 3: Handle view options that only need downloaded assets (-a/-d/-s) ===
+# Also collect install option (-S) for later use
+while getopts 'adhsSvu:' o; do case "$o" in
+ 'a') echo "$AGENT"; Throw 'Done.' ;;
+ 'd') echo "$DAEMON"; Throw 'Done.' ;;
+ 'h') Help ;;
+ 's') echo "$ASCPT"; Throw 'Done.' ;;
+ 'S') USESCRIPT=1 ;;
+ 'u') ;; # Already handled in phase 1
+ 'v') ;; # Already handled in phase 1
+esac; done; shift $(($OPTIND - 1))
+
+# === Phase 4: Resolve target user (only needed for actual installation) ===
+
+# Apply user override if specified via -u
+if [ -n "$FORUSER_OVERRIDE" ]; then
+ FORUSER="$FORUSER_OVERRIDE"
+fi
+
+# Validate FORUSER
+if [ -z "$FORUSER" ] || [ "$FORUSER" = "root" ]; then
+ Throw "Cannot determine target user. In MDM context, use -u to specify the target user."
+fi
+
+FORUID=$(id -u "$FORUSER" 2>/dev/null) || Throw "User '$FORUSER' not found on this system"
+
+# Resolve user's home directory for preference path
+# Always use dscl when FORUSER differs from current user (covers both root and non-root with -u)
+if [ "$FORUSER" != "$(id -un)" ]; then
+ user_home=$(dscl . -read "/Users/$FORUSER" NFSHomeDirectory 2>/dev/null | sed 's/^NFSHomeDirectory:[[:space:]]*//')
+ [ -z "$user_home" ] && Throw "Cannot resolve home directory for user $FORUSER"
+else
+ user_home="$HOME"
+fi
+user_pref_path="${user_home}/Library/Preferences/$FULL_NAME"
+
+# === Phase 5: Install ===
+
+IsServiceLoaded "${FULL_NAME}_service" && \
+ Throw "${FULL_NAME}_service is already loaded"
+
+# EnsureConfig creates preference dirs/files only when actually installing (no side effects for view options)
+EnsureConfig
+
+Msg "******* STARTING SERVICE INSTALLATION *******"
+[ $USESCRIPT -eq 1 ] && InstallViaAppleScript || InstallViaShell
+KickstartServerGUI
+
+Msg "Done. Note - if you move the app, you will have to reinstall or edit the LaunchDaemon and LaunchAgent plists"
+ls -l "$agent_path" "$daemon_path"
+
+```
+
+
+
+## When Do You Need This Script?
+
+For normal desktop installations, just click the "Install" button inside the RustDesk app. This script is designed for:
+
+- Remote deployment via SSH (no GUI access)
+- MDM batch deployment (postinstall script)
+- VM environments (headless setup)
+- Any scenario where you need to automate service registration without user interaction
+
+## What the Script Does
+
+The script installs two launchd plist files and loads them:
+
+| Component | Plist Path | Runs As | Purpose |
+|-----------|-----------|---------|---------|
+| Daemon | `/Library/LaunchDaemons/com.carriez._service.plist` | root | IPC config management service — runs at boot, no login required |
+| Agent | `/Library/LaunchAgents/com.carriez._server.plist` | user | RustDesk server process — handles screen capture, input, audio, remote connections |
+
+Both are set to auto-start: the daemon starts at boot, the agent starts at the login screen (LoginWindow session) and persists through user login (Aqua session).
+
+## What the Script Does NOT Do
+
+The script does not grant macOS privacy permissions. Screen Recording, Accessibility, and Input Monitoring permissions must be granted separately:
+
+- Manually: System Settings > Privacy & Security
+- MDM: Deploy a PPPC (Privacy Preferences Policy Control) configuration profile
+
+Without these permissions, the service will run, but remote control will not work properly.
+
+## Prerequisites
+
+- macOS with the app already installed in `/Applications/` (or a subfolder)
+- Admin privileges (`sudo`)
+- Internet access (to download plist templates from GitHub)
+
+## Quick Start
+
+### Standard RustDesk
+
+```bash
+sudo bash install_service.sh
+```
+
+### Custom Client
+
+Edit the `DEFAULTPATH` variable at the top of the script:
+
+```bash
+DEFAULTPATH='/Applications/MyCustomApp.app'
+```
+
+Everything else (app name, plist labels, config paths) is auto-derived from this single path:
+
+| Item | Example (`DEFAULTPATH = /Applications/MyApp.app`) |
+|------|---------------------------------------------------|
+| APP_NAME | `MyApp` |
+| Plist label | `com.carriez.MyApp_service` / `com.carriez.MyApp_server` |
+| Config dir | `~/Library/Preferences/com.carriez.MyApp/` |
+| Config files | `MyApp.toml`, `MyApp2.toml` |
+
+> The app's bundle identifier (e.g., `com.mycompany.myapp`) does not affect these paths. RustDesk always uses `com.carriez.` internally.
+
+## Options
+
+```
+Usage: install_service.sh [options]
+
+ -i id Set the RustDesk ID (does not install service)
+ -p pwd Set a permanent password (does not install service)
+ -u user Specify target user (for MDM/headless context)
+ -S Use AppleScript to install (not recommended)
+ -a Show generated Agent plist (view only)
+ -d Show generated Daemon plist (view only)
+ -s Show generated AppleScript (view only)
+ -v List installed plist files
+ -h Help
+```
+
+### Examples
+
+```bash
+# Set ID and password (service must already be installed)
+sudo bash install_service.sh -i 123456789 -p "MySecurePassword"
+
+# Install the auto-start service
+sudo bash install_service.sh
+
+# MDM deployment — specify the target user explicitly
+sudo bash install_service.sh -u johndoe
+
+# Preview the generated daemon plist without installing
+bash install_service.sh -d
+```
+
+## Configuration Flags
+
+Two flags at the top of the script control install behavior:
+
+| Flag | Default | Description |
+|------|---------|-------------|
+| `AUTO_CREATE_CONFIG` | `1` | Auto-create empty config files if they don't exist. Set to `0` to require pre-configured toml files. |
+| `CLEAR_STOP_SERVICE` | `0` | Remove the `stop-service` flag from existing config on reinstall. Set to `1` for MDM redeployment to a machine that previously had the service uninstalled. |
+
+## Re-running the Script
+
+If the service is already loaded, the script exits immediately with:
+
+```
+com.carriez._service is already loaded
+```
+
+This is safe — existing plist files and auto-start are not affected. To force reinstall, unload the service first:
+
+```bash
+sudo launchctl bootout system/com.carriez._service
+```
+
+## Uninstalling
+
+```bash
+# Replace with your app name (e.g., RustDesk, MyApp)
+sudo launchctl bootout system/com.carriez._service
+sudo launchctl bootout gui/$(id -u)/com.carriez._server
+sudo rm /Library/LaunchDaemons/com.carriez._service.plist
+sudo rm /Library/LaunchAgents/com.carriez._server.plist
+```
+
+## Troubleshooting
+
+| Problem | Solution |
+|---------|----------|
+| "Cannot determine target user" | In MDM context, use `-u ` to specify the user |
+| "Could not find app installed in /Applications" | The script retries every 30 seconds. Ensure the `.app` bundle exists in `/Applications/` or a subfolder |
+| Service loaded but not connecting | Set `CLEAR_STOP_SERVICE=1` and reinstall — a previous uninstall may have left a `stop-service` flag in config |
+| Remote control not working after install | Privacy permissions not granted. Deploy a PPPC profile via MDM, or grant Screen Recording / Accessibility / Input Monitoring manually in System Settings |
+