#!/usr/bin/env bash # Install RustDesk if needed, then point it at your own ID/relay server. # Safe to re-run. Without --package, a machine that already has RustDesk is only re-pointed; # one without RustDesk gets the latest official release. # # Usage (as root): # install-helpdesk.sh --host id.example.com --key 'AbCd...=' [--relay HOST] [--api URL] # install-helpdesk.sh --config '' # Options: # --password PW set a permanent password for unattended access # --random-password generate one and print it # --package FILE|URL install this .deb/.rpm (e.g. your own build), even over an # existing RustDesk (upgrade) # Enrolling in the CortenDesk console (rustdesk --assign), all optional: # --enroll-token TOKEN API token with Manage on devices (+ strategies / address books # if you assign those). Or set CORTENDESK_ENROLL_TOKEN. # --prompt-enroll ask for the token (hidden) and, if no field below is given, # the owner and device group; Enter at the token prompt skips # --enroll-user NAME --device-group NAME --strategy NAME --address-book NAME # --device-name NAME --note TEXT # Without a token, a user who signs in to the console from the RustDesk client claims it. # # Supports Debian/Ubuntu (apt), Fedora/RHEL (dnf/yum) and openSUSE (zypper) on x86_64 and aarch64. set -euo pipefail usage() { sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; } host="" key="" relay="" api="" config="" password="" random_pw="" package="" enroll_token="${CORTENDESK_ENROLL_TOKEN:-}" prompt_enroll="" enroll_user="" device_group="" strategy="" address_book="" device_name="" note="" while [ $# -gt 0 ]; do case "$1" in --host) host="${2:-}"; shift 2 ;; --key) key="${2:-}"; shift 2 ;; --relay) relay="${2:-}"; shift 2 ;; --api) api="${2:-}"; shift 2 ;; --config) config="${2:-}"; shift 2 ;; --password) password="${2:-}"; shift 2 ;; --random-password) random_pw=1; shift ;; --package) package="${2:-}"; shift 2 ;; --enroll-token) enroll_token="${2:-}"; shift 2 ;; --prompt-enroll) prompt_enroll=1; shift ;; --enroll-user) enroll_user="${2:-}"; shift 2 ;; --device-group) device_group="${2:-}"; shift 2 ;; --strategy) strategy="${2:-}"; shift 2 ;; --address-book) address_book="${2:-}"; shift 2 ;; --device-name) device_name="${2:-}"; shift 2 ;; --note) note="${2:-}"; shift 2 ;; -h|--help) usage 0 ;; *) echo "Unknown argument: $1" >&2; usage 1 ;; esac done [ "$(id -u)" -eq 0 ] || { echo "Run as root (sudo)." >&2; exit 1; } # Ask before installing, so whoever runs the kit answers once and can walk away. # Only when a terminal can really be opened: RMM tools, cron and CI have none. if [ -n "$prompt_enroll" ] && [ -z "$enroll_token" ] && { : < /dev/tty; } 2>/dev/null; then printf 'CortenDesk enrollment token (Enter to skip enrollment): ' > /dev/tty IFS= read -rs enroll_token < /dev/tty || true echo > /dev/tty if [ -n "$enroll_token" ] && [ -z "$enroll_user$device_group$strategy$address_book$device_name$note" ]; then printf 'Console user who owns this device (Enter for none): ' > /dev/tty IFS= read -r enroll_user < /dev/tty || true printf 'Device group (Enter for none): ' > /dev/tty IFS= read -r device_group < /dev/tty || true fi fi # `rustdesk --assign` registers this device with the console's /api/devices/cli. The # console replies with nothing on success (the client prints "Done!") and with a short # reason otherwise, e.g. "User 'x' not found." or a missing token permission. assign=() if [ -n "$enroll_token" ]; then assign=(--assign --token "$enroll_token") [ -z "$enroll_user" ] || assign+=(--user_name "$enroll_user") [ -z "$device_group" ] || assign+=(--device_group_name "$device_group") [ -z "$strategy" ] || assign+=(--strategy_name "$strategy") [ -z "$address_book" ] || assign+=(--address_book_name "$address_book") [ -z "$device_name" ] || assign+=(--device_name "$device_name") [ -z "$note" ] || assign+=(--note "$note") if [ "${#assign[@]}" -eq 3 ]; then echo "Enrollment needs at least one of --enroll-user, --device-group, --strategy," \ "--address-book, --device-name or --note." >&2 exit 1 fi fi if [ -z "$config" ]; then [ -n "$host" ] && [ -n "$key" ] || { echo "Give --config, or --host and --key." >&2; usage 1; } for v in "$host" "$key" "$relay" "$api"; do case "$v" in *[\"\\[:space:]]*) echo "Value must not contain quotes, backslashes or spaces: $v" >&2; exit 1 ;; esac done # Same format as helpdesk/tools/make-config.sh: JSON -> base64url (no padding) -> reversed. config=$(printf '{"host":"%s","relay":"%s","api":"%s","key":"%s"}' "$host" "$relay" "$api" "$key" \ | base64 | tr -d '\n' | tr -- '+/' '-_' | tr -d '=' | rev) fi latest_package_url() { local arch pattern arch=$(uname -m) case "$arch" in x86_64|aarch64) ;; *) echo "Unsupported architecture: $arch" >&2; exit 1 ;; esac case "$1" in apt) pattern="-${arch}\\.deb\$" ;; dnf) pattern="\\.${arch}\\.rpm\$" ;; zypper) pattern="\\.${arch}-suse\\.rpm\$" ;; esac curl -fsSL -H 'User-Agent: helpdesk-installer' https://api.github.com/repos/rustdesk/rustdesk/releases/latest \ | grep -o '"browser_download_url": *"[^"]*"' | sed 's/.*"\(https[^"]*\)"/\1/' \ | grep -E -- "$pattern" | head -n1 || true } install_rustdesk() { local pm url file if command -v apt-get >/dev/null; then pm=apt elif command -v dnf >/dev/null || command -v yum >/dev/null; then pm=dnf elif command -v zypper >/dev/null; then pm=zypper else echo "No supported package manager (apt, dnf/yum, zypper) found." >&2; exit 1 fi src="$package" [ -n "$src" ] || src=$(latest_package_url "$pm") [ -n "$src" ] || { echo "Could not find a RustDesk package for this system; use --package." >&2; exit 1; } file="$(mktemp -d)/$(basename "${src%%\?*}")" if [ -f "$src" ]; then cp "$src" "$file" else echo "Downloading $src" curl -fsSL -o "$file" "$src" fi echo "Installing RustDesk..." case "$pm" in # --reinstall so a same-version build replaces a stock RustDesk package. apt) apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y --reinstall "$file" ;; dnf) if command -v dnf >/dev/null; then dnf install -y "$file"; else yum install -y "$file"; fi ;; zypper) zypper --non-interactive --no-gpg-checks install "$file" ;; esac rm -rf "$(dirname "$file")" } if [ -n "$package" ] || ! command -v rustdesk >/dev/null; then install_rustdesk else echo "RustDesk already installed; re-pointing it at your server." fi systemctl enable --now rustdesk >/dev/null 2>&1 || true sleep 3 out=$(rustdesk --config "$config" 2>&1 || true) case "$out" in *required*) echo "Applying the server config failed: $out" >&2; exit 1 ;; esac echo "Server config applied." if [ -n "$random_pw" ] && [ -z "$password" ]; then password=$(tr -dc 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789' &1 || true) case "$out" in *Done*) ;; *) echo "Setting the permanent password failed: $out" >&2; exit 1 ;; esac fi # Restart so the service re-registers with the new server right away. systemctl restart rustdesk >/dev/null 2>&1 || true rd_id="" for _ in 1 2 3 4 5 6 7 8 9 10; do sleep 3 rd_id=$(rustdesk --get-id 2>/dev/null | tr -d '[:space:]' || true) [ -n "$rd_id" ] && break done enrolled="" if [ "${#assign[@]}" -gt 0 ]; then out=$(rustdesk "${assign[@]}" 2>&1 || true) case "$out" in *Done*) enrolled=1 ;; *) echo "Enrolling the device in the console failed: $out" >&2; exit 1 ;; esac fi echo echo "RustDesk ID: $rd_id" [ -z "$password" ] || echo "Password: $password" if [ -n "$enrolled" ]; then echo "Console: enrolled" else echo "Console: not enrolled here. Sign in from RustDesk (Settings > Account) with a console account to claim it." fi