From 2f1a51da1ea382eb40d55d4b64b2772208a79ad8 Mon Sep 17 00:00:00 2001 From: Kirill Knize Date: Tue, 21 Jul 2026 14:18:27 +0200 Subject: [PATCH] CLI-871 Add MDM deployment scripts as public reference Mirrors the JumpCloud install/update/rollback/remove scripts for macOS, Linux, and Windows, so the customer-facing MDM deployment guide can link to a reviewable source instead of internal tooling. --- mdm-scripts/direct-sonar-cdn/linux/install.sh | 134 +++++++++++++++++ mdm-scripts/direct-sonar-cdn/linux/remove.sh | 61 ++++++++ .../direct-sonar-cdn/linux/rollback.sh | 101 +++++++++++++ mdm-scripts/direct-sonar-cdn/linux/update.sh | 121 ++++++++++++++++ mdm-scripts/direct-sonar-cdn/macos/install.sh | 136 ++++++++++++++++++ mdm-scripts/direct-sonar-cdn/macos/remove.sh | 61 ++++++++ .../direct-sonar-cdn/macos/rollback.sh | 102 +++++++++++++ mdm-scripts/direct-sonar-cdn/macos/update.sh | 122 ++++++++++++++++ .../direct-sonar-cdn/windows/install.ps1 | 130 +++++++++++++++++ .../direct-sonar-cdn/windows/remove.ps1 | 36 +++++ .../direct-sonar-cdn/windows/rollback.ps1 | 77 ++++++++++ .../direct-sonar-cdn/windows/update.ps1 | 98 +++++++++++++ 12 files changed, 1179 insertions(+) create mode 100755 mdm-scripts/direct-sonar-cdn/linux/install.sh create mode 100755 mdm-scripts/direct-sonar-cdn/linux/remove.sh create mode 100755 mdm-scripts/direct-sonar-cdn/linux/rollback.sh create mode 100755 mdm-scripts/direct-sonar-cdn/linux/update.sh create mode 100755 mdm-scripts/direct-sonar-cdn/macos/install.sh create mode 100755 mdm-scripts/direct-sonar-cdn/macos/remove.sh create mode 100755 mdm-scripts/direct-sonar-cdn/macos/rollback.sh create mode 100755 mdm-scripts/direct-sonar-cdn/macos/update.sh create mode 100644 mdm-scripts/direct-sonar-cdn/windows/install.ps1 create mode 100644 mdm-scripts/direct-sonar-cdn/windows/remove.ps1 create mode 100644 mdm-scripts/direct-sonar-cdn/windows/rollback.ps1 create mode 100644 mdm-scripts/direct-sonar-cdn/windows/update.ps1 diff --git a/mdm-scripts/direct-sonar-cdn/linux/install.sh b/mdm-scripts/direct-sonar-cdn/linux/install.sh new file mode 100755 index 000000000..521c7cc91 --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/linux/install.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# 1. Always installs the latest CLI to /usr/local/bin/sonar. +# 2. For every local user that has a standalone install, replaces it with a +# symlink to the MDM binary so PATH order cannot override it. +# Self-contained: no external scripts are downloaded or executed. +# SONARQUBE_CLI_APPROVED_VERSION -- optional JumpCloud variable; pins the install version instead of +# using stable.version. +set -euo pipefail + +MDM_BINARY="/usr/local/bin/sonar" +STANDALONE_REL=".local/share/sonarqube-cli/bin/sonar" +readonly ARTIFACT_BASE_URL="https://binaries.sonarsource.com/Distribution/sonarqube-cli" +# JumpCloud-specific: {{SONARQUBE_CLI_APPROVED_VERSION}} is replaced with the Custom Variable's +# value (quoted) at runtime -- not a real environment variable. Other MDM systems may inject this +# differently. +APPROVED_VERSION_OVERRIDE={{SONARQUBE_CLI_APPROVED_VERSION}} +[[ "$APPROVED_VERSION_OVERRIDE" == \{\{*\}\} ]] && APPROVED_VERSION_OVERRIDE="" # defensive: unattached JumpCloud var may leave the literal placeholder +readonly HTTPS_ONLY="=https" +export PATH="/usr/local/bin:$PATH" + +# ── Platform helpers ────────────────────────────────────────────────────────── + +cdn_os() { echo "linux"; } + +cdn_platform() { + case "$(uname -m)" in + aarch64|arm64) echo "linux-arm64" ;; + x86_64|amd64) echo "linux-x86-64" ;; + *) echo "" ;; + esac +} + +# ── SHA256 helpers ──────────────────────────────────────────────────────────── + +sha256_of() { + local file="$1" + sha256sum "$file" | awk '{print $1}' +} + +verify_sha256() { + local binary="$1" version="$2" + local platform os base url expected actual + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Warning: unsupported platform — skipping SHA256 check." >&2 + return + fi + base="sonarqube-cli-${version}-${platform}" + url="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin.sha256" + expected="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url" 2>/dev/null | awk '{print $1}')" + if [[ -z "$expected" ]]; then + echo "Error: could not fetch SHA256 from $url — aborting." >&2 + exit 1 + fi + actual="$(sha256_of "$binary")" + if [[ "$actual" != "$expected" ]]; then + echo "Error: SHA256 mismatch for $binary" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + exit 1 + fi + echo "SHA256 verified: $binary ($version)" +} + +# ── Binary download ─────────────────────────────────────────────────────────── + +download_binary() { + local version="$1" dest="$2" + local platform os base url_bin url_exe tmp_bin + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Error: unsupported platform." >&2; exit 1 + fi + base="sonarqube-cli-${version}-${platform}" + url_bin="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin" + url_exe="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.exe" + tmp_bin="$(mktemp /tmp/sonar-bin.XXXXXX)" + trap 'rm -f "$tmp_bin"' EXIT + echo "Downloading sonarqube-cli from:" + if curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_bin" -o "$tmp_bin" 2>/dev/null; then + echo " $url_bin" + elif curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_exe" -o "$tmp_bin" 2>/dev/null; then + echo " $url_exe" + else + echo "Error: could not download sonarqube-cli." >&2; rm -f "$tmp_bin"; exit 1 + fi + chmod +x "$tmp_bin" + verify_sha256 "$tmp_bin" "$version" + trap - EXIT + mv "$tmp_bin" "$dest" +} + +# ── Step 1: install MDM binary ─────────────────────────────────────────────── + +echo "Installing SonarQube CLI to $MDM_BINARY..." + +if [[ -n "$APPROVED_VERSION_OVERRIDE" ]]; then + version="$APPROVED_VERSION_OVERRIDE" +else + version="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "${ARTIFACT_BASE_URL}/stable.version" | tr -d '[:space:]')" +fi +echo "Target version: $version" + +mkdir -p "$(dirname "$MDM_BINARY")" +download_binary "$version" "$MDM_BINARY" + +# ── Step 2: enumerate local user home directories ──────────────────────────── + +get_user_homes() { + getent passwd | awk -F: '$6 ~ /^\/home\// { print $6 }' +} + +# ── Step 3: symlink standalone path → MDM binary for each user ─────────────── + +while IFS= read -r home; do + standalone="$home/$STANDALONE_REL" + [[ -e "$standalone" || -L "$standalone" ]] || continue + + if [[ -L "$standalone" && "$(readlink "$standalone")" == "$MDM_BINARY" ]]; then + echo " $standalone — already symlinked, skipping" + continue + fi + + echo " $standalone — replacing with symlink to $MDM_BINARY" + ln -sf "$MDM_BINARY" "$standalone" +done < <(get_user_homes) + +# ── Verify ─────────────────────────────────────────────────────────────────── + +echo "MDM sonar: $MDM_BINARY — $("$MDM_BINARY" --version)" +echo "which sonar: $(which sonar)" +echo "sonar -v: $(sonar --version)" diff --git a/mdm-scripts/direct-sonar-cdn/linux/remove.sh b/mdm-scripts/direct-sonar-cdn/linux/remove.sh new file mode 100755 index 000000000..bf5521014 --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/linux/remove.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# 1. Removes the MDM-managed binary. +# 2. Keeps removing wherever sonar resolves in system PATH until nothing is found. +# 3. Removes standalone installs from every local user's home directory. +set -euo pipefail + +MDM_BINARY="/usr/local/bin/sonar" +STANDALONE_REL=".local/share/sonarqube-cli/bin/sonar" +export PATH="/usr/local/bin:$PATH" + +# -- Remove MDM binary --------------------------------------------------------- + +rm -f "$MDM_BINARY" + +if [[ -f "$MDM_BINARY" ]]; then + echo "Error: failed to remove $MDM_BINARY" >&2 + exit 1 +fi +echo "MDM binary removed from $MDM_BINARY." + +# -- Remove any remaining resolvable copies (system PATH) ---------------------- +# Catches binaries in Homebrew, manually added PATH entries, etc. + +while true; do + sonar_path="$(command -v sonar 2>/dev/null || true)" + [[ -z "$sonar_path" ]] && break + if ! "$sonar_path" --version 2>/dev/null | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + echo "Error: $sonar_path does not look like the SonarQube CLI (unexpected --version output) -- refusing to remove it." >&2 + exit 1 + fi + echo "sonar still resolves to: $sonar_path -- removing..." + rm -f "$sonar_path" + if [[ -e "$sonar_path" ]]; then + echo "Error: failed to remove $sonar_path" >&2 + exit 1 + fi +done + +# -- Remove standalone installs from all local user home directories ----------- +# These are not in SYSTEM's PATH so command -v cannot find them. + +get_user_homes() { + case "$(uname -s)" in + Darwin) + dscl . list /Users NFSHomeDirectory \ + | awk '$1 !~ /^_/ && $2 ~ /^\/Users\// { print $2 }' + ;; + Linux) + getent passwd | awk -F: '$6 ~ /^\/home\// { print $6 }' + ;; + esac +} + +while IFS= read -r home; do + standalone="$home/$STANDALONE_REL" + [[ -e "$standalone" || -L "$standalone" ]] || continue + echo " $standalone -- removing..." + rm -f "$standalone" +done < <(get_user_homes) + +echo "which sonar: not found" diff --git a/mdm-scripts/direct-sonar-cdn/linux/rollback.sh b/mdm-scripts/direct-sonar-cdn/linux/rollback.sh new file mode 100755 index 000000000..c6c281d7b --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/linux/rollback.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Ensures the MDM binary is present, then rolls back to SONARQUBE_CLI_ROLLBACK_VERSION. +# SONARQUBE_CLI_ROLLBACK_VERSION — required JumpCloud variable (e.g. 1.1.0.3122). +set -euo pipefail + +MDM_BINARY="/usr/local/bin/sonar" +readonly ARTIFACT_BASE_URL="https://binaries.sonarsource.com/Distribution/sonarqube-cli" +readonly HTTPS_ONLY="=https" +export PATH="/usr/local/bin:$PATH" +# JumpCloud-specific: {{SONARQUBE_CLI_ROLLBACK_VERSION}} is replaced with the Custom Variable's +# value (quoted) at runtime -- not a real environment variable. Other MDM systems may inject this +# differently. +ROLLBACK_VERSION={{SONARQUBE_CLI_ROLLBACK_VERSION}} +[[ "$ROLLBACK_VERSION" == \{\{*\}\} ]] && ROLLBACK_VERSION="" # defensive: unattached JumpCloud var may leave the literal placeholder +: "${ROLLBACK_VERSION:?SONARQUBE_CLI_ROLLBACK_VERSION must be set via JumpCloud variable injection}" + +# ── SHA256 helpers ──────────────────────────────────────────────────────────── + +sha256_of() { + local file="$1" + sha256sum "$file" | awk '{print $1}' +} + +cdn_os() { echo "linux"; } + +cdn_platform() { + case "$(uname -m)" in + aarch64|arm64) echo "linux-arm64" ;; + x86_64|amd64) echo "linux-x86-64" ;; + *) echo "" ;; + esac +} + +verify_sha256() { + local binary="$1" version="$2" + local platform os base url expected actual + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Warning: unsupported platform — skipping SHA256 check." >&2 + return + fi + base="sonarqube-cli-${version}-${platform}" + url="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin.sha256" + expected="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url" 2>/dev/null | awk '{print $1}')" + if [[ -z "$expected" ]]; then + echo "Error: could not fetch SHA256 from $url — aborting." >&2 + exit 1 + fi + actual="$(sha256_of "$binary")" + if [[ "$actual" != "$expected" ]]; then + echo "Error: SHA256 mismatch for $binary" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + exit 1 + fi + echo "SHA256 verified: $binary ($version)" +} + +# ── Binary download ─────────────────────────────────────────────────────────── + +download_binary() { + local version="$1" dest="$2" + local platform os base url_bin url_exe tmp_bin + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Error: unsupported platform." >&2; exit 1 + fi + base="sonarqube-cli-${version}-${platform}" + url_bin="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin" + url_exe="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.exe" + tmp_bin="$(mktemp /tmp/sonar-bin.XXXXXX)" + trap 'rm -f "$tmp_bin"' EXIT + echo "Downloading sonarqube-cli from:" + if curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_bin" -o "$tmp_bin" 2>/dev/null; then + echo " $url_bin" + elif curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_exe" -o "$tmp_bin" 2>/dev/null; then + echo " $url_exe" + else + echo "Error: could not download sonarqube-cli." >&2; rm -f "$tmp_bin"; exit 1 + fi + chmod +x "$tmp_bin" + verify_sha256 "$tmp_bin" "$version" + trap - EXIT + mv "$tmp_bin" "$dest" +} + +# ───────────────────────────────────────────────────────────────────────────── + +if [[ ! -f "$MDM_BINARY" ]]; then + echo "MDM binary not found — installing target version directly..." + mkdir -p "$(dirname "$MDM_BINARY")" + download_binary "$ROLLBACK_VERSION" "$MDM_BINARY" +else + download_binary "$ROLLBACK_VERSION" "$MDM_BINARY" +fi + +echo "MDM sonar: $MDM_BINARY — $("$MDM_BINARY" --version)" +echo "which sonar: $(which sonar)" +echo "sonar -v: $(sonar --version)" diff --git a/mdm-scripts/direct-sonar-cdn/linux/update.sh b/mdm-scripts/direct-sonar-cdn/linux/update.sh new file mode 100755 index 000000000..a5abb8503 --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/linux/update.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# Ensures the MDM binary is present, then updates it to the latest version. +# Downloads directly -- no sonar self-update, no external script fetches. +# SONARQUBE_CLI_FORCE -- optional JumpCloud variable; set to "true" to replace even when already at latest. +# SONARQUBE_CLI_APPROVED_VERSION -- optional JumpCloud variable; pins the update version instead of +# using stable.version. +set -euo pipefail + +MDM_BINARY="/usr/local/bin/sonar" +readonly ARTIFACT_BASE_URL="https://binaries.sonarsource.com/Distribution/sonarqube-cli" +# JumpCloud-specific: {{SONARQUBE_CLI_FORCE}} / {{SONARQUBE_CLI_APPROVED_VERSION}} are replaced with +# the Custom Variable's value (quoted) at runtime -- not real environment variables. Other MDM +# systems may inject this differently. +APPROVED_VERSION_OVERRIDE={{SONARQUBE_CLI_APPROVED_VERSION}} +[[ "$APPROVED_VERSION_OVERRIDE" == \{\{*\}\} ]] && APPROVED_VERSION_OVERRIDE="" # defensive: unattached JumpCloud var may leave the literal placeholder +FORCE={{SONARQUBE_CLI_FORCE}} +readonly HTTPS_ONLY="=https" +export PATH="/usr/local/bin:$PATH" + +# -- Platform helpers ---------------------------------------------------------- + +cdn_os() { echo "linux"; } + +cdn_platform() { + case "$(uname -m)" in + aarch64|arm64) echo "linux-arm64" ;; + x86_64|amd64) echo "linux-x86-64" ;; + *) echo "" ;; + esac +} + +# -- SHA256 helpers ------------------------------------------------------------ + +sha256_of() { + local file="$1" + sha256sum "$file" | awk '{print $1}' +} + +verify_sha256() { + local binary="$1" version="$2" + local platform os base url expected actual + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Warning: unsupported platform -- skipping SHA256 check." >&2 + return + fi + base="sonarqube-cli-${version}-${platform}" + url="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin.sha256" + expected="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url" 2>/dev/null | awk '{print $1}')" + if [[ -z "$expected" ]]; then + echo "Error: could not fetch SHA256 from $url -- aborting." >&2 + exit 1 + fi + actual="$(sha256_of "$binary")" + if [[ "$actual" != "$expected" ]]; then + echo "Error: SHA256 mismatch for $binary" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + exit 1 + fi + echo "SHA256 verified: $binary ($version)" +} + +# -- Binary download ----------------------------------------------------------- + +download_binary() { + local version="$1" dest="$2" + local platform os base url_bin url_exe tmp_bin + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Error: unsupported platform." >&2; exit 1 + fi + base="sonarqube-cli-${version}-${platform}" + url_bin="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin" + url_exe="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.exe" + tmp_bin="$(mktemp /tmp/sonar-bin.XXXXXX)" + trap 'rm -f "$tmp_bin"' EXIT + echo "Downloading sonarqube-cli from:" + if curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_bin" -o "$tmp_bin" 2>/dev/null; then + echo " $url_bin" + elif curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_exe" -o "$tmp_bin" 2>/dev/null; then + echo " $url_exe" + else + echo "Error: could not download sonarqube-cli." >&2; rm -f "$tmp_bin"; exit 1 + fi + chmod +x "$tmp_bin" + verify_sha256 "$tmp_bin" "$version" + trap - EXIT + mv "$tmp_bin" "$dest" +} + +# -- Main ---------------------------------------------------------------------- + +if [[ -n "$APPROVED_VERSION_OVERRIDE" ]]; then + target_version="$APPROVED_VERSION_OVERRIDE" +else + target_version="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "${ARTIFACT_BASE_URL}/stable.version" | tr -d '[:space:]')" +fi +echo "Target version: $target_version" + +target_semver="$(echo "$target_version" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+')" + +if [[ -f "$MDM_BINARY" ]]; then + installed_semver="$("$MDM_BINARY" --version 2>/dev/null | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+' || true)" + if [[ "${FORCE:-}" != "true" && "$installed_semver" == "$target_semver" ]]; then + echo "Already at version $target_version, nothing to do." + exit 0 + fi + echo "Updating $installed_semver -> $target_semver..." +else + echo "MDM binary not found -- installing first..." + mkdir -p "$(dirname "$MDM_BINARY")" +fi + +download_binary "$target_version" "$MDM_BINARY" + +echo "MDM sonar: $MDM_BINARY -- $("$MDM_BINARY" --version)" +echo "which sonar: $(which sonar)" +echo "sonar -v: $(sonar --version)" diff --git a/mdm-scripts/direct-sonar-cdn/macos/install.sh b/mdm-scripts/direct-sonar-cdn/macos/install.sh new file mode 100755 index 000000000..99ac8c43d --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/macos/install.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# 1. Always installs the latest CLI to /usr/local/bin/sonar. +# 2. For every local user that has a standalone install, replaces it with a +# symlink to the MDM binary so PATH order cannot override it. +# Self-contained: no external scripts are downloaded or executed. +# SONARQUBE_CLI_APPROVED_VERSION -- optional JumpCloud variable; pins the install version instead of +# using stable.version. +set -euo pipefail + +MDM_BINARY="/usr/local/bin/sonar" +STANDALONE_REL=".local/share/sonarqube-cli/bin/sonar" +readonly ARTIFACT_BASE_URL="https://binaries.sonarsource.com/Distribution/sonarqube-cli" +# JumpCloud-specific: {{SONARQUBE_CLI_APPROVED_VERSION}} is replaced with the Custom Variable's +# value (quoted) at runtime -- not a real environment variable. Other MDM systems may inject this +# differently. +APPROVED_VERSION_OVERRIDE={{SONARQUBE_CLI_APPROVED_VERSION}} +[[ "$APPROVED_VERSION_OVERRIDE" == \{\{*\}\} ]] && APPROVED_VERSION_OVERRIDE="" # defensive: unattached JumpCloud var may leave the literal placeholder +readonly HTTPS_ONLY="=https" +export PATH="/usr/local/bin:$PATH" + +# ── Platform helpers ────────────────────────────────────────────────────────── + +cdn_os() { echo "macos"; } + +cdn_platform() { + case "$(uname -m)" in + arm64) echo "macos-arm64" ;; + x86_64) echo "macos-x86-64" ;; + *) echo "" ;; + esac +} + +# ── SHA256 helpers ──────────────────────────────────────────────────────────── + +sha256_of() { + local file="$1" + shasum -a 256 "$file" | awk '{print $1}' +} + +verify_sha256() { + local binary="$1" version="$2" + local platform os base url expected actual + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Warning: unsupported platform — skipping SHA256 check." >&2 + return + fi + base="sonarqube-cli-${version}-${platform}" + url="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin.sha256" + expected="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url" 2>/dev/null | awk '{print $1}')" + if [[ -z "$expected" ]]; then + echo "Error: could not fetch SHA256 from $url — aborting." >&2 + exit 1 + fi + actual="$(sha256_of "$binary")" + if [[ "$actual" != "$expected" ]]; then + echo "Error: SHA256 mismatch for $binary" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + exit 1 + fi + echo "SHA256 verified: $binary ($version)" +} + +# ── Binary download ─────────────────────────────────────────────────────────── + +download_binary() { + local version="$1" dest="$2" + local platform os base url_bin url_exe tmp_bin + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Error: unsupported platform." >&2; exit 1 + fi + base="sonarqube-cli-${version}-${platform}" + url_bin="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin" + url_exe="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.exe" + tmp_bin="$(mktemp /tmp/sonar-bin.XXXXXX)" + trap 'rm -f "$tmp_bin"' EXIT + echo "Downloading sonarqube-cli from:" + if curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_bin" -o "$tmp_bin" 2>/dev/null; then + echo " $url_bin" + elif curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_exe" -o "$tmp_bin" 2>/dev/null; then + echo " $url_exe" + else + echo "Error: could not download sonarqube-cli." >&2; rm -f "$tmp_bin"; exit 1 + fi + chmod +x "$tmp_bin" + [[ "$platform" == macos-* ]] && xattr -d com.apple.quarantine "$tmp_bin" 2>/dev/null || true + verify_sha256 "$tmp_bin" "$version" + trap - EXIT + mv "$tmp_bin" "$dest" +} + +# ── Step 1: install MDM binary ─────────────────────────────────────────────── + +echo "Installing SonarQube CLI to $MDM_BINARY..." + +if [[ -n "$APPROVED_VERSION_OVERRIDE" ]]; then + version="$APPROVED_VERSION_OVERRIDE" +else + version="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "${ARTIFACT_BASE_URL}/stable.version" | tr -d '[:space:]')" +fi +echo "Target version: $version" + +mkdir -p "$(dirname "$MDM_BINARY")" +download_binary "$version" "$MDM_BINARY" + +# ── Step 2: enumerate local user home directories ──────────────────────────── + +get_user_homes() { + dscl . list /Users NFSHomeDirectory \ + | awk '$1 !~ /^_/ && $2 ~ /^\/Users\// { print $2 }' +} + +# ── Step 3: symlink standalone path → MDM binary for each user ─────────────── + +while IFS= read -r home; do + standalone="$home/$STANDALONE_REL" + [[ -e "$standalone" || -L "$standalone" ]] || continue + + if [[ -L "$standalone" && "$(readlink "$standalone")" == "$MDM_BINARY" ]]; then + echo " $standalone — already symlinked, skipping" + continue + fi + + echo " $standalone — replacing with symlink to $MDM_BINARY" + ln -sf "$MDM_BINARY" "$standalone" +done < <(get_user_homes) + +# ── Verify ─────────────────────────────────────────────────────────────────── + +echo "MDM sonar: $MDM_BINARY — $("$MDM_BINARY" --version)" +echo "which sonar: $(which sonar)" +echo "sonar -v: $(sonar --version)" diff --git a/mdm-scripts/direct-sonar-cdn/macos/remove.sh b/mdm-scripts/direct-sonar-cdn/macos/remove.sh new file mode 100755 index 000000000..bf5521014 --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/macos/remove.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# 1. Removes the MDM-managed binary. +# 2. Keeps removing wherever sonar resolves in system PATH until nothing is found. +# 3. Removes standalone installs from every local user's home directory. +set -euo pipefail + +MDM_BINARY="/usr/local/bin/sonar" +STANDALONE_REL=".local/share/sonarqube-cli/bin/sonar" +export PATH="/usr/local/bin:$PATH" + +# -- Remove MDM binary --------------------------------------------------------- + +rm -f "$MDM_BINARY" + +if [[ -f "$MDM_BINARY" ]]; then + echo "Error: failed to remove $MDM_BINARY" >&2 + exit 1 +fi +echo "MDM binary removed from $MDM_BINARY." + +# -- Remove any remaining resolvable copies (system PATH) ---------------------- +# Catches binaries in Homebrew, manually added PATH entries, etc. + +while true; do + sonar_path="$(command -v sonar 2>/dev/null || true)" + [[ -z "$sonar_path" ]] && break + if ! "$sonar_path" --version 2>/dev/null | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + echo "Error: $sonar_path does not look like the SonarQube CLI (unexpected --version output) -- refusing to remove it." >&2 + exit 1 + fi + echo "sonar still resolves to: $sonar_path -- removing..." + rm -f "$sonar_path" + if [[ -e "$sonar_path" ]]; then + echo "Error: failed to remove $sonar_path" >&2 + exit 1 + fi +done + +# -- Remove standalone installs from all local user home directories ----------- +# These are not in SYSTEM's PATH so command -v cannot find them. + +get_user_homes() { + case "$(uname -s)" in + Darwin) + dscl . list /Users NFSHomeDirectory \ + | awk '$1 !~ /^_/ && $2 ~ /^\/Users\// { print $2 }' + ;; + Linux) + getent passwd | awk -F: '$6 ~ /^\/home\// { print $6 }' + ;; + esac +} + +while IFS= read -r home; do + standalone="$home/$STANDALONE_REL" + [[ -e "$standalone" || -L "$standalone" ]] || continue + echo " $standalone -- removing..." + rm -f "$standalone" +done < <(get_user_homes) + +echo "which sonar: not found" diff --git a/mdm-scripts/direct-sonar-cdn/macos/rollback.sh b/mdm-scripts/direct-sonar-cdn/macos/rollback.sh new file mode 100755 index 000000000..f990f6f77 --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/macos/rollback.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# Ensures the MDM binary is present, then rolls back to SONARQUBE_CLI_ROLLBACK_VERSION. +# SONARQUBE_CLI_ROLLBACK_VERSION — required JumpCloud variable (e.g. 1.1.0.3122). +set -euo pipefail + +MDM_BINARY="/usr/local/bin/sonar" +readonly ARTIFACT_BASE_URL="https://binaries.sonarsource.com/Distribution/sonarqube-cli" +readonly HTTPS_ONLY="=https" +export PATH="/usr/local/bin:$PATH" +# JumpCloud-specific: {{SONARQUBE_CLI_ROLLBACK_VERSION}} is replaced with the Custom Variable's +# value (quoted) at runtime -- not a real environment variable. Other MDM systems may inject this +# differently. +ROLLBACK_VERSION={{SONARQUBE_CLI_ROLLBACK_VERSION}} +[[ "$ROLLBACK_VERSION" == \{\{*\}\} ]] && ROLLBACK_VERSION="" # defensive: unattached JumpCloud var may leave the literal placeholder +: "${ROLLBACK_VERSION:?SONARQUBE_CLI_ROLLBACK_VERSION must be set via JumpCloud variable injection}" + +# ── SHA256 helpers ──────────────────────────────────────────────────────────── + +sha256_of() { + local file="$1" + shasum -a 256 "$file" | awk '{print $1}' +} + +cdn_os() { echo "macos"; } + +cdn_platform() { + case "$(uname -m)" in + arm64) echo "macos-arm64" ;; + x86_64) echo "macos-x86-64" ;; + *) echo "" ;; + esac +} + +verify_sha256() { + local binary="$1" version="$2" + local platform os base url expected actual + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Warning: unsupported platform — skipping SHA256 check." >&2 + return + fi + base="sonarqube-cli-${version}-${platform}" + url="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin.sha256" + expected="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url" 2>/dev/null | awk '{print $1}')" + if [[ -z "$expected" ]]; then + echo "Error: could not fetch SHA256 from $url — aborting." >&2 + exit 1 + fi + actual="$(sha256_of "$binary")" + if [[ "$actual" != "$expected" ]]; then + echo "Error: SHA256 mismatch for $binary" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + exit 1 + fi + echo "SHA256 verified: $binary ($version)" +} + +# ── Binary download ─────────────────────────────────────────────────────────── + +download_binary() { + local version="$1" dest="$2" + local platform os base url_bin url_exe tmp_bin + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Error: unsupported platform." >&2; exit 1 + fi + base="sonarqube-cli-${version}-${platform}" + url_bin="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin" + url_exe="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.exe" + tmp_bin="$(mktemp /tmp/sonar-bin.XXXXXX)" + trap 'rm -f "$tmp_bin"' EXIT + echo "Downloading sonarqube-cli from:" + if curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_bin" -o "$tmp_bin" 2>/dev/null; then + echo " $url_bin" + elif curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_exe" -o "$tmp_bin" 2>/dev/null; then + echo " $url_exe" + else + echo "Error: could not download sonarqube-cli." >&2; rm -f "$tmp_bin"; exit 1 + fi + chmod +x "$tmp_bin" + [[ "$platform" == macos-* ]] && xattr -d com.apple.quarantine "$tmp_bin" 2>/dev/null || true + verify_sha256 "$tmp_bin" "$version" + trap - EXIT + mv "$tmp_bin" "$dest" +} + +# ───────────────────────────────────────────────────────────────────────────── + +if [[ ! -f "$MDM_BINARY" ]]; then + echo "MDM binary not found — installing target version directly..." + mkdir -p "$(dirname "$MDM_BINARY")" + download_binary "$ROLLBACK_VERSION" "$MDM_BINARY" +else + download_binary "$ROLLBACK_VERSION" "$MDM_BINARY" +fi + +echo "MDM sonar: $MDM_BINARY — $("$MDM_BINARY" --version)" +echo "which sonar: $(which sonar)" +echo "sonar -v: $(sonar --version)" diff --git a/mdm-scripts/direct-sonar-cdn/macos/update.sh b/mdm-scripts/direct-sonar-cdn/macos/update.sh new file mode 100755 index 000000000..8f322cb8c --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/macos/update.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Ensures the MDM binary is present, then updates it to the latest version. +# Downloads directly -- no sonar self-update, no external script fetches. +# SONARQUBE_CLI_FORCE -- optional JumpCloud variable; set to "true" to replace even when already at latest. +# SONARQUBE_CLI_APPROVED_VERSION -- optional JumpCloud variable; pins the update version instead of +# using stable.version. +set -euo pipefail + +MDM_BINARY="/usr/local/bin/sonar" +readonly ARTIFACT_BASE_URL="https://binaries.sonarsource.com/Distribution/sonarqube-cli" +# JumpCloud-specific: {{SONARQUBE_CLI_FORCE}} / {{SONARQUBE_CLI_APPROVED_VERSION}} are replaced with +# the Custom Variable's value (quoted) at runtime -- not real environment variables. Other MDM +# systems may inject this differently. +APPROVED_VERSION_OVERRIDE={{SONARQUBE_CLI_APPROVED_VERSION}} +[[ "$APPROVED_VERSION_OVERRIDE" == \{\{*\}\} ]] && APPROVED_VERSION_OVERRIDE="" # defensive: unattached JumpCloud var may leave the literal placeholder +FORCE={{SONARQUBE_CLI_FORCE}} +readonly HTTPS_ONLY="=https" +export PATH="/usr/local/bin:$PATH" + +# -- Platform helpers ---------------------------------------------------------- + +cdn_os() { echo "macos"; } + +cdn_platform() { + case "$(uname -m)" in + arm64) echo "macos-arm64" ;; + x86_64) echo "macos-x86-64" ;; + *) echo "" ;; + esac +} + +# -- SHA256 helpers ------------------------------------------------------------ + +sha256_of() { + local file="$1" + shasum -a 256 "$file" | awk '{print $1}' +} + +verify_sha256() { + local binary="$1" version="$2" + local platform os base url expected actual + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Warning: unsupported platform -- skipping SHA256 check." >&2 + return + fi + base="sonarqube-cli-${version}-${platform}" + url="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin.sha256" + expected="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url" 2>/dev/null | awk '{print $1}')" + if [[ -z "$expected" ]]; then + echo "Error: could not fetch SHA256 from $url -- aborting." >&2 + exit 1 + fi + actual="$(sha256_of "$binary")" + if [[ "$actual" != "$expected" ]]; then + echo "Error: SHA256 mismatch for $binary" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + exit 1 + fi + echo "SHA256 verified: $binary ($version)" +} + +# -- Binary download ----------------------------------------------------------- + +download_binary() { + local version="$1" dest="$2" + local platform os base url_bin url_exe tmp_bin + platform="$(cdn_platform)" + os="$(cdn_os)" + if [[ -z "$platform" || -z "$os" ]]; then + echo "Error: unsupported platform." >&2; exit 1 + fi + base="sonarqube-cli-${version}-${platform}" + url_bin="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.bin" + url_exe="${ARTIFACT_BASE_URL}/${version}/${os}/${base}.exe" + tmp_bin="$(mktemp /tmp/sonar-bin.XXXXXX)" + trap 'rm -f "$tmp_bin"' EXIT + echo "Downloading sonarqube-cli from:" + if curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_bin" -o "$tmp_bin" 2>/dev/null; then + echo " $url_bin" + elif curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "$url_exe" -o "$tmp_bin" 2>/dev/null; then + echo " $url_exe" + else + echo "Error: could not download sonarqube-cli." >&2; rm -f "$tmp_bin"; exit 1 + fi + chmod +x "$tmp_bin" + [[ "$platform" == macos-* ]] && xattr -d com.apple.quarantine "$tmp_bin" 2>/dev/null || true + verify_sha256 "$tmp_bin" "$version" + trap - EXIT + mv "$tmp_bin" "$dest" +} + +# -- Main ---------------------------------------------------------------------- + +if [[ -n "$APPROVED_VERSION_OVERRIDE" ]]; then + target_version="$APPROVED_VERSION_OVERRIDE" +else + target_version="$(curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" "${ARTIFACT_BASE_URL}/stable.version" | tr -d '[:space:]')" +fi +echo "Target version: $target_version" + +target_semver="$(echo "$target_version" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+')" + +if [[ -f "$MDM_BINARY" ]]; then + installed_semver="$("$MDM_BINARY" --version 2>/dev/null | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+' || true)" + if [[ "${FORCE:-}" != "true" && "$installed_semver" == "$target_semver" ]]; then + echo "Already at version $target_version, nothing to do." + exit 0 + fi + echo "Updating $installed_semver -> $target_semver..." +else + echo "MDM binary not found -- installing first..." + mkdir -p "$(dirname "$MDM_BINARY")" +fi + +download_binary "$target_version" "$MDM_BINARY" + +echo "MDM sonar: $MDM_BINARY -- $("$MDM_BINARY" --version)" +echo "which sonar: $(which sonar)" +echo "sonar -v: $(sonar --version)" diff --git a/mdm-scripts/direct-sonar-cdn/windows/install.ps1 b/mdm-scripts/direct-sonar-cdn/windows/install.ps1 new file mode 100644 index 000000000..bc0495dfb --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/windows/install.ps1 @@ -0,0 +1,130 @@ +#Requires -Version 5.1 +# 1. Always installs the latest CLI to C:\Program Files\sonarqube-cli\bin. +# 2. For every local user that has a standalone install, replaces it with a +# symbolic link to the MDM binary so PATH order cannot override it. +# Self-contained: no external scripts are downloaded or executed. +# SONARQUBE_CLI_APPROVED_VERSION -- optional JumpCloud variable; pins the install version instead of +# using stable.version. +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +$MdmBinary = 'C:\Program Files\sonarqube-cli\bin\sonar.exe' +$StandaloneRel = 'AppData\Local\sonarqube-cli\bin\sonar.exe' +$ArtifactBaseUrl = 'https://binaries.sonarsource.com/Distribution/sonarqube-cli' +# JumpCloud-specific: {{SONARQUBE_CLI_APPROVED_VERSION}} is replaced with the Custom Variable's +# value (quoted) at runtime -- not a real $env: variable. Other MDM systems may inject this +# differently. +$ApprovedVersionOverride = {{SONARQUBE_CLI_APPROVED_VERSION}} +if ($ApprovedVersionOverride -isnot [string]) { $ApprovedVersionOverride = $null } +$Platform = 'windows-x86-64' +$env:PATH = "$(Split-Path $MdmBinary);$env:PATH" + +# -- Helpers ------------------------------------------------------------------- + +function Get-TargetVersion { + if ($ApprovedVersionOverride) { return $ApprovedVersionOverride } + (Invoke-WebRequest -Uri "$ArtifactBaseUrl/stable.version" -UseBasicParsing).Content.Trim() +} + +function Test-BinarySha256 { + param([string]$Binary, [string]$Version) + $base = "sonarqube-cli-$Version-$Platform" + $url = "$ArtifactBaseUrl/$Version/windows/$base.exe.sha256" + try { + $raw = (Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Stop).Content + # Server sends this file as binary/octet-stream, so Windows PowerShell 5.1 + # returns Content as byte[] instead of a string -- decode it explicitly. + if ($raw -is [byte[]]) { $raw = [System.Text.Encoding]::UTF8.GetString($raw) } + $expected = ($raw.Trim() -split '\s+')[0].ToLower() + } catch { + Write-Error "Could not fetch SHA256 from $url -- aborting.`n $($_.Exception.Message)" + exit 1 + } + $actual = (Get-FileHash -Path $Binary -Algorithm SHA256).Hash.ToLower() + if ($actual -ne $expected) { + Write-Error "SHA256 mismatch for $Binary`n expected: $expected`n actual: $actual" + exit 1 + } + Write-Output "SHA256 verified: $Binary ($Version)" +} + +function Install-SonarBinary { + param([string]$Version, [string]$Dest) + $base = "sonarqube-cli-$Version-$Platform" + $url = "$ArtifactBaseUrl/$Version/windows/$base.exe" + $tmp = [System.IO.Path]::GetTempFileName() + Write-Output 'Downloading sonarqube-cli from:' + Write-Output " $url" + try { + Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing + Test-BinarySha256 -Binary $tmp -Version $Version + $dir = Split-Path $Dest + if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } + Move-Item $tmp $Dest -Force + # Grant read+execute to all users -- binary is installed by SYSTEM so + # default ACL may not include regular user access. + & icacls $Dest /grant 'Authenticated Users:(RX)' | Out-Null + } catch { + Remove-Item $tmp -Force -ErrorAction SilentlyContinue + throw + } +} + +function Add-ToMachinePath { + param([string]$Dir) + # Use the [Environment] APIs rather than Get-ItemProperty/Set-ItemProperty: Get-ItemProperty + # expands REG_EXPAND_SZ values (e.g. %SystemRoot%), so writing that back would permanently + # bake resolved paths into the machine PATH and lose the original %variable% references. + $current = [Environment]::GetEnvironmentVariable('Path', 'Machine') + if (($current -split ';') -contains $Dir) { + Write-Output 'PATH already contains the install directory, skipping.' + return + } + $newPath = $current.TrimEnd(';') + ';' + $Dir + [Environment]::SetEnvironmentVariable('Path', $newPath, 'Machine') + Write-Output "Added to Machine PATH: $Dir" + # Broadcast environment change so explorer and running shells pick up the new PATH + # without requiring a logoff/logon. + $sig = '[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] +public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);' + Add-Type -MemberDefinition $sig -Name 'Win32SendMsg' -Namespace 'NativeMethods' -ErrorAction SilentlyContinue + $result = [UIntPtr]::Zero + [NativeMethods.Win32SendMsg]::SendMessageTimeout([IntPtr]0xFFFF, 0x1A, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$result) | Out-Null +} + +# -- Step 1: install MDM binary ------------------------------------------------ + +Write-Host "Installing SonarQube CLI to $MdmBinary..." + +$version = Get-TargetVersion +Write-Host "Target version: $version" + +Install-SonarBinary -Version $version -Dest $MdmBinary +Add-ToMachinePath -Dir (Split-Path $MdmBinary) + +# -- Step 2: symlink standalone path -> MDM binary for each local user ---------- +# Only acts when the standalone binary already exists. +# A user's sonar self-update will overwrite the symlink with a real binary; +# the daily MDM run re-symlinks it on the next execution. + +Get-ChildItem 'C:\Users' -Directory -ErrorAction SilentlyContinue | ForEach-Object { + $standalone = Join-Path $_.FullName $StandaloneRel + if (-not (Test-Path $standalone) -and -not (Test-Path $standalone -PathType Leaf)) { return } + + $item = Get-Item $standalone -ErrorAction SilentlyContinue + if ($item -and $item.LinkType -eq 'SymbolicLink' -and $item.Target -eq $MdmBinary) { + Write-Host " $standalone -- already symlinked, skipping" + return + } + + Write-Host " $standalone -- replacing with symlink to $MdmBinary" + if ($item) { Remove-Item $standalone -Force } + New-Item -ItemType SymbolicLink -Path $standalone -Target $MdmBinary -Force | Out-Null +} + +# -- Verify -------------------------------------------------------------------- + +Write-Host "MDM sonar: $MdmBinary -- $(& $MdmBinary --version)" +$sonarWhere = try { (Get-Command sonar -ErrorAction Stop).Source } catch { 'not found' } +Write-Host "where sonar: $sonarWhere" +if ($sonarWhere -ne 'not found') { Write-Host "sonar -v: $(sonar --version)" } diff --git a/mdm-scripts/direct-sonar-cdn/windows/remove.ps1 b/mdm-scripts/direct-sonar-cdn/windows/remove.ps1 new file mode 100644 index 000000000..1c2651977 --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/windows/remove.ps1 @@ -0,0 +1,36 @@ +#Requires -Version 5.1 +# Removes the MDM-managed binary, then keeps removing wherever sonar resolves +# until 'where sonar' returns nothing. +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +$MdmBinary = 'C:\Program Files\sonarqube-cli\bin\sonar.exe' +$env:PATH = "$(Split-Path $MdmBinary);$env:PATH" + +# Remove MDM binary +Remove-Item $MdmBinary -Force -ErrorAction SilentlyContinue + +if (Test-Path $MdmBinary) { + Write-Error "Failed to remove $MdmBinary" + exit 1 +} +Write-Host "MDM binary removed from $MdmBinary." + +# Enforcing: keep removing wherever sonar resolves until it is completely gone. +while ($true) { + $sonarPath = try { (Get-Command sonar -ErrorAction Stop).Source } catch { $null } + if (-not $sonarPath) { break } + $versionOutput = try { (& $sonarPath --version 2>$null | Out-String) } catch { '' } + if ($versionOutput -notmatch '\d+\.\d+\.\d+') { + Write-Error "$sonarPath does not look like the SonarQube CLI (unexpected --version output) -- refusing to remove it." + exit 1 + } + Write-Host "sonar still resolves to: $sonarPath -- removing..." + Remove-Item $sonarPath -Force -ErrorAction SilentlyContinue + if (Test-Path $sonarPath) { + Write-Error "Failed to remove $sonarPath" + exit 1 + } +} + +Write-Host "where sonar: not found" diff --git a/mdm-scripts/direct-sonar-cdn/windows/rollback.ps1 b/mdm-scripts/direct-sonar-cdn/windows/rollback.ps1 new file mode 100644 index 000000000..626a719b2 --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/windows/rollback.ps1 @@ -0,0 +1,77 @@ +#Requires -Version 5.1 +# Ensures the MDM binary is present, then rolls back to SONARQUBE_CLI_ROLLBACK_VERSION. +# SONARQUBE_CLI_ROLLBACK_VERSION -- required JumpCloud variable (e.g. 1.1.0.3122). +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +$MdmBinary = 'C:\Program Files\sonarqube-cli\bin\sonar.exe' +$ArtifactBaseUrl = 'https://binaries.sonarsource.com/Distribution/sonarqube-cli' +$Platform = 'windows-x86-64' +# JumpCloud-specific: {{SONARQUBE_CLI_ROLLBACK_VERSION}} is replaced with the Custom Variable's +# value (quoted) at runtime -- not a real $env: variable. Other MDM systems may inject this +# differently. +$RollbackVersion = {{SONARQUBE_CLI_ROLLBACK_VERSION}} +if ($RollbackVersion -isnot [string]) { $RollbackVersion = $null } +$env:PATH = "$(Split-Path $MdmBinary);$env:PATH" + +if (-not $RollbackVersion) { + Write-Error 'SONARQUBE_CLI_ROLLBACK_VERSION must be set via JumpCloud variable injection.' + exit 1 +} + +# -- Helpers ------------------------------------------------------------------- + +function Test-BinarySha256 { + param([string]$Binary, [string]$Version) + $base = "sonarqube-cli-$Version-$Platform" + $url = "$ArtifactBaseUrl/$Version/windows/$base.exe.sha256" + try { + $raw = (Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Stop).Content + # Server sends this file as binary/octet-stream, so Windows PowerShell 5.1 + # returns Content as byte[] instead of a string -- decode it explicitly. + if ($raw -is [byte[]]) { $raw = [System.Text.Encoding]::UTF8.GetString($raw) } + $expected = ($raw.Trim() -split '\s+')[0].ToLower() + } catch { + Write-Error "Could not fetch SHA256 from $url -- aborting.`n $($_.Exception.Message)" + exit 1 + } + $actual = (Get-FileHash -Path $Binary -Algorithm SHA256).Hash.ToLower() + if ($actual -ne $expected) { + Write-Error "SHA256 mismatch for $Binary`n expected: $expected`n actual: $actual" + exit 1 + } + Write-Output "SHA256 verified: $Binary ($Version)" +} + +function Install-SonarBinary { + param([string]$Version, [string]$Dest) + $base = "sonarqube-cli-$Version-$Platform" + $url = "$ArtifactBaseUrl/$Version/windows/$base.exe" + $tmp = [System.IO.Path]::GetTempFileName() + Write-Output 'Downloading sonarqube-cli from:' + Write-Output " $url" + try { + Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing + Test-BinarySha256 -Binary $tmp -Version $Version + $dir = Split-Path $Dest + if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } + Move-Item $tmp $Dest -Force + # Grant read+execute to all users -- binary is installed by SYSTEM so + # default ACL may not include regular user access. + & icacls $Dest /grant 'Authenticated Users:(RX)' | Out-Null + } catch { + Remove-Item $tmp -Force -ErrorAction SilentlyContinue + throw + } +} + +# -- Main ---------------------------------------------------------------------- + +Write-Host "Rolling back to version $RollbackVersion..." + +Install-SonarBinary -Version $RollbackVersion -Dest $MdmBinary + +Write-Host "MDM sonar: $MdmBinary -- $(& $MdmBinary --version)" +$sonarWhere = try { (Get-Command sonar -ErrorAction Stop).Source } catch { 'not found' } +Write-Host "where sonar: $sonarWhere" +if ($sonarWhere -ne 'not found') { Write-Host "sonar -v: $(sonar --version)" } diff --git a/mdm-scripts/direct-sonar-cdn/windows/update.ps1 b/mdm-scripts/direct-sonar-cdn/windows/update.ps1 new file mode 100644 index 000000000..903bff086 --- /dev/null +++ b/mdm-scripts/direct-sonar-cdn/windows/update.ps1 @@ -0,0 +1,98 @@ +#Requires -Version 5.1 +# Ensures the MDM binary is present, then updates it to the latest version. +# SONARQUBE_CLI_FORCE -- optional JumpCloud variable; set to "true" to replace even when already at latest. +# SONARQUBE_CLI_APPROVED_VERSION -- optional JumpCloud variable; pins the update version instead of +# using stable.version. +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +$MdmBinary = 'C:\Program Files\sonarqube-cli\bin\sonar.exe' +$ArtifactBaseUrl = 'https://binaries.sonarsource.com/Distribution/sonarqube-cli' +# JumpCloud-specific: {{SONARQUBE_CLI_FORCE}} / {{SONARQUBE_CLI_APPROVED_VERSION}} are replaced with +# the Custom Variable's value at runtime -- not real $env: variables. Other MDM systems may inject +# this differently. +# Note: JumpCloud quotes String-typed variables (e.g. SONARQUBE_CLI_APPROVED_VERSION) but +# substitutes a Boolean-typed variable (SONARQUBE_CLI_FORCE) as an unquoted bareword (true/false), so we quote +# it ourselves below to keep it a valid string literal. +$ApprovedVersionOverride = {{SONARQUBE_CLI_APPROVED_VERSION}} +if ($ApprovedVersionOverride -isnot [string]) { $ApprovedVersionOverride = $null } +$Platform = 'windows-x86-64' +$ForceUpdate = '{{SONARQUBE_CLI_FORCE}}' -eq 'true' +$env:PATH = "$(Split-Path $MdmBinary);$env:PATH" + +# -- Helpers ------------------------------------------------------------------- + +function Get-TargetVersion { + if ($ApprovedVersionOverride) { return $ApprovedVersionOverride } + (Invoke-WebRequest -Uri "$ArtifactBaseUrl/stable.version" -UseBasicParsing).Content.Trim() +} + +function Test-BinarySha256 { + param([string]$Binary, [string]$Version) + $base = "sonarqube-cli-$Version-$Platform" + $url = "$ArtifactBaseUrl/$Version/windows/$base.exe.sha256" + try { + $raw = (Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Stop).Content + # Server sends this file as binary/octet-stream, so Windows PowerShell 5.1 + # returns Content as byte[] instead of a string -- decode it explicitly. + if ($raw -is [byte[]]) { $raw = [System.Text.Encoding]::UTF8.GetString($raw) } + $expected = ($raw.Trim() -split '\s+')[0].ToLower() + } catch { + Write-Error "Could not fetch SHA256 from $url -- aborting.`n $($_.Exception.Message)" + exit 1 + } + $actual = (Get-FileHash -Path $Binary -Algorithm SHA256).Hash.ToLower() + if ($actual -ne $expected) { + Write-Error "SHA256 mismatch for $Binary`n expected: $expected`n actual: $actual" + exit 1 + } + Write-Output "SHA256 verified: $Binary ($Version)" +} + +function Install-SonarBinary { + param([string]$Version, [string]$Dest) + $base = "sonarqube-cli-$Version-$Platform" + $url = "$ArtifactBaseUrl/$Version/windows/$base.exe" + $tmp = [System.IO.Path]::GetTempFileName() + Write-Output 'Downloading sonarqube-cli from:' + Write-Output " $url" + try { + Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing + Test-BinarySha256 -Binary $tmp -Version $Version + $dir = Split-Path $Dest + if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } + Move-Item $tmp $Dest -Force + # Grant read+execute to all users -- binary is installed by SYSTEM so + # default ACL may not include regular user access. + & icacls $Dest /grant 'Authenticated Users:(RX)' | Out-Null + } catch { + Remove-Item $tmp -Force -ErrorAction SilentlyContinue + throw + } +} + +# -- Main ---------------------------------------------------------------------- + +$targetVersion = Get-TargetVersion +Write-Host "Target version: $targetVersion" + +$targetSemver = ($targetVersion -split '\.')[0..2] -join '.' + +if (Test-Path $MdmBinary) { + $installedRaw = (& $MdmBinary --version 2>$null | Out-String) + $installedSemver = ([regex]'\d+\.\d+\.\d+').Match($installedRaw).Value + if (-not $ForceUpdate -and $installedSemver -eq $targetSemver) { + Write-Host "Already at version $targetVersion, nothing to do." + exit 0 + } + Write-Host "Updating $installedSemver -> $targetSemver..." +} else { + Write-Host 'MDM binary not found -- installing first...' +} + +Install-SonarBinary -Version $targetVersion -Dest $MdmBinary + +Write-Host "MDM sonar: $MdmBinary -- $(& $MdmBinary --version)" +$sonarWhere = try { (Get-Command sonar -ErrorAction Stop).Source } catch { 'not found' } +Write-Host "where sonar: $sonarWhere" +if ($sonarWhere -ne 'not found') { Write-Host "sonar -v: $(sonar --version)" }