Skip to content

Instantly share code, notes, and snippets.

@engalar
Created June 16, 2025 07:00
Show Gist options
  • Save engalar/b04be00c2616dbec06b70369f34aba24 to your computer and use it in GitHub Desktop.
Save engalar/b04be00c2616dbec06b70369f34aba24 to your computer and use it in GitHub Desktop.
#!/bin/bash
# ====================================================================================
#
# Mendix Studio Pro Offline Installer Preparation Tool (v5 - Stderr/Stdout Fix)
#
# Description:
# This script prepares a full offline installer package for Mendix Studio Pro
# on a Linux system, for later installation on Windows.
#
# Fixes in this version:
# - Fixed a bug where the interactive menu was not displayed because its stdout
# was being captured by command substitution.
# - Redirected all user prompts and menus to stderr (>&2) to ensure they
# are always visible in interactive mode.
#
# ====================================================================================
# --- Script Setup ---
set -o errexit
set -o nounset
set -o pipefail
# --- Color Definitions for Output ---
C_RESET='\033[0m'
C_RED='\033[0;31m'
C_GREEN='\033[0;32m'
C_YELLOW='\033[0;33m'
C_BLUE='\033[0;34m'
C_BOLD='\033[1m'
# --- Global Configuration ---
OUTPUT_DIR_BASE="Mendix_Offline_Installer"
DEPS_SUBDIR="Dependencies"
MENDIX_LISTING_URL="https://cdn.mendix.com/listing.txt"
MENDIX_ARTIFACTS_URL="https://artifacts.rnd.mendix.com/modelers"
TEMP_LISTING_FILE=$(mktemp)
CONNECT_TIMEOUT=15
READ_TIMEOUT=90
# --- Dependency Definitions (Global Associative Array) ---
declare -gA DEPS
# --- Helper Functions ---
log_info() { echo -e "${C_BLUE}INFO:${C_RESET} $1"; }
log_success() { echo -e "${C_GREEN}SUCCESS:${C_RESET} $1"; }
log_warn() { echo -e "${C_YELLOW}WARN:${C_RESET} $1" >&2; } # Warnings go to stderr
log_error() { echo -e "${C_RED}ERROR:${C_RESET} $1" >&2; } # Errors go to stderr
check_tools() {
log_info "Checking for required tools (wget, curl, etc.)..."
local missing=0
for tool in wget curl grep sed cut tail sort; do
if ! command -v "${tool}" &>/dev/null; then
log_error "Required tool '${tool}' is not installed."
missing=1
fi
done
[[ ${missing} -eq 1 ]] && {
log_error "Please install missing tools and try again."
exit 1
}
log_success "All required tools are present."
}
check_network() {
log_info "Checking network connectivity by trying to reach google.com..."
if ! curl -s --head --connect-timeout 10 "https://www.google.com" >/dev/null; then
log_error "Network connectivity check failed. The script cannot reach the internet."
log_error "Please check your network connection, DNS settings, or corporate firewall/proxy rules."
exit 1
fi
log_success "Network connectivity is OK."
}
download_final_file() {
local url="$1"
local target_path="$2"
local filename
filename=$(basename "${target_path}")
if [[ -f "${target_path}" ]]; then
log_warn "File '${filename}' already exists. Skipping download."
else
log_info "Downloading '${filename}'..."
if ! wget --connect-timeout=${CONNECT_TIMEOUT} --read-timeout=${READ_TIMEOUT} -q --show-progress -O "${target_path}" "${url}"; then
log_error "Failed to download ${filename}. Please check the URL and your network."
rm -f "${target_path}"
exit 1
fi
log_success "'${filename}' downloaded."
fi
}
resolve_dependencies() {
local mendix_version="$1"
local major_version
major_version=$(echo "${mendix_version}" | cut -d. -f1)
log_info "Resolving dependencies for Mendix v${major_version}..."
# Common Dependencies
DEPS[vcredist2019_url]="https://aka.ms/vs/17/release/vc_redist.x64.exe"
DEPS[vcredist2019_target]="vcredist2019_x64.exe"
DEPS[native_builder_url]="https://cdn.mendix.com/native-builder/latest.exe"
DEPS[native_builder_target]="mendix_native_mobile_builder.exe"
DEPS[git_url]="https://github.com/git-for-windows/git/releases/download/v2.45.1.windows.1/Git-2.45.1-64-bit.exe"
DEPS[git_target]="git_for_windows_installer.exe"
DEPS[webview_url]="https://go.microsoft.com/fwlink/p/?LinkId=2124703"
DEPS[webview_target]="MicrosoftEdgeWebView2Setup.exe"
case "${major_version}" in
10)
DEPS[net_runtime_url]="https://download.visualstudio.microsoft.com/download/pr/982c6c39-0186-449e-bab6-1a52319853f8/47a25a322972986f8516d3f3747d32c9/windowsdesktop-runtime-8.0.4-win-x64.exe"
DEPS[net_runtime_target]="windowsdesktop-runtime-8.0-x64.exe"
DEPS[jdk_url]="https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.3%2B9/OpenJDK21U-jdk_x64_windows_hotspot_21.0.3_9.msi"
DEPS[jdk_target]="adoptiumjdk_21_x64.msi"
DEPS[gradle_url]="https://services.gradle.org/distributions/gradle-8.5-bin.zip"
DEPS[gradle_target]="gradle-8.5-bin.zip"
;;
9)
DEPS[net_runtime_url]="https://download.visualstudio.microsoft.com/download/pr/5681bdf9-0a48-45ac-b7bf-21b7b61657aa/bbdc43bc7bf0d15b97c1a98ae2e82ec0/windowsdesktop-runtime-6.0.5-win-x64.exe"
DEPS[net_runtime_target]="windowsdesktop-runtime-6.0-x64.exe"
local jdk_mirror_url="https://mirrors.tuna.tsinghua.edu.cn/Adoptium/11/jdk/x64/windows/"
log_info "Dynamically finding latest JDK 11 from Tsinghua mirror..."
local latest_jdk_file
latest_jdk_file=$(curl --connect-timeout=${CONNECT_TIMEOUT} -s "${jdk_mirror_url}" | grep -o 'href="OpenJDK11U-jdk_x64_windows_hotspot_[^"]*\.msi"' | sed 's/href="//;s/"$//' | tail -n 1)
if [[ -z "${latest_jdk_file}" ]]; then
log_error "Failed to dynamically find the JDK 11 MSI. Aborting."
exit 1
fi
DEPS[jdk_url]="${jdk_mirror_url}${latest_jdk_file}"
DEPS[jdk_target]="adoptopenjdk_11_x64.msi"
DEPS[gradle_url]="https://services.gradle.org/distributions/gradle-7.6-bin.zip"
DEPS[gradle_target]="gradle-7.6-bin.zip"
DEPS[vcredist2010_url]="https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe"
DEPS[vcredist2010_target]="vcredist2010_x64.exe"
;;
*)
log_error "Unsupported Mendix major version: '${major_version}'. This script supports versions 9 and 10."
exit 1
;;
esac
log_success "Dependency list for Mendix v${major_version} resolved."
}
# --- Core Logic Functions ---
get_mendix_version_interactively() {
# All logging and user prompts in this function must go to stderr (>&2)
# so they are not captured by the command substitution in main().
log_info "Fetching available Mendix Studio Pro versions..." >&2
if ! wget --connect-timeout=${CONNECT_TIMEOUT} --read-timeout=${READ_TIMEOUT} -q -O "${TEMP_LISTING_FILE}" "${MENDIX_LISTING_URL}"; then
log_error "Failed to download the Mendix version list. Please check the URL and your network."
exit 1
fi
log_success "Version list downloaded." >&2
mapfile -t versions < <(grep 'runtime/mendix' "${TEMP_LISTING_FILE}" | sed 's/^runtime\/mendix-\(.*\)\.tar\.gz$/\1/' | sort -rV)
if [ ${#versions[@]} -eq 0 ]; then
log_error "Could not retrieve any Mendix versions from the listing."
exit 1
fi
# *** FIX: Redirect menu and prompts to stderr (>&2) ***
echo -e "\n${C_BOLD}Please select a Mendix Studio Pro version to prepare:${C_RESET}" >&2
for i in "${!versions[@]}"; do
printf "[%3d] %s\n" "$((i + 1))" "${versions[$i]}" >&2
done
local choice
while true; do
# read -p already prints its prompt to stderr, so no need for redirection here.
read -p "$(echo -e "\nEnter the number of the version you want: ")" choice
if [[ "${choice}" =~ ^[0-9]+$ ]] && [ "${choice}" -ge 1 ] && [ "${choice}" -le "${#versions[@]}" ]; then
# *** The final selected version is echoed to STDOUT to be captured by main() ***
echo "${versions[$((choice - 1))]}"
break
else
log_warn "Invalid input. Please enter a number between 1 and ${#versions[@]}."
fi
done
}
# --- Main Execution Function ---
main() {
trap 'rm -f "${TEMP_LISTING_FILE}"' EXIT
check_tools
check_network
local mendix_version=""
if [ $# -eq 1 ]; then
mendix_version="$1"
log_info "Direct mode: Preparing offline installer for version ${C_YELLOW}${mendix_version}${C_RESET}."
else
# The interactive menu is now correctly displayed.
mendix_version=$(get_mendix_version_interactively)
log_info "Interactive mode: Selected version ${C_YELLOW}${mendix_version}${C_RESET}."
fi
resolve_dependencies "${mendix_version}"
local output_dir="${OUTPUT_DIR_BASE}_v${mendix_version}"
local deps_dir="${output_dir}/${DEPS_SUBDIR}"
log_info "Creating directory structure in '${output_dir}'..."
rm -rf "${output_dir}"
mkdir -p "${deps_dir}"
log_info "--- Starting Downloads ---"
local mendix_setup_url="${MENDIX_ARTIFACTS_URL}/Mendix-${mendix_version}-Setup.exe"
local mendix_setup_target="${output_dir}/Mendix-${mendix_version}-Setup.exe"
download_final_file "${mendix_setup_url}" "${mendix_setup_target}"
log_info "Processing all required dependencies..."
for key in "${!DEPS[@]}"; do
if [[ $key == *_url ]]; then
local prefix="${key%_url}"
local url="${DEPS[${prefix}_url]}"
local target_filename="${DEPS[${prefix}_target]}"
local target_path="${deps_dir}/${target_filename}"
download_final_file "${url}" "${target_path}"
fi
done
echo
log_success "========================================================="
log_success "✅ Offline Installer Preparation for Mendix ${mendix_version} Complete! ✅"
log_success "========================================================="
echo -e "Resources are located in the '${C_YELLOW}${output_dir}${C_RESET}' directory."
echo -e "\nFinal directory structure:"
if command -v tree &>/dev/null; then
tree "${output_dir}"
else
ls -lR "${output_dir}"
fi
echo -e "\n${C_BOLD}Next Steps:${C_RESET}"
echo -e "1. Copy the entire '${C_YELLOW}${output_dir}${C_RESET}' directory to your Windows machine."
echo -e "2. On Windows, run the Mendix Setup executable from that folder."
}
# --- SCRIPT ENTRY POINT ---
main "$@v"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment