Automating Developer Environments: One Script to Rule Them All

Automating Developer Environments: One Script to Rule Them All

In today's fast-paced tech world, developers often find themselves working across multiple languages, frameworks, and tools. Whether you're developing web applications in the morning, mobile apps in the afternoon, and desktop software in the evening, maintaining a consistent and efficient development environment can be a significant challenge.


1. The Multi-Platform Development Challenge


Last week, I found myself staring at my bloated MacBook, realizing how much time I'd spent over the years manually installing and configuring development tools. Every new project seemed to require another SDK, runtime, or application. The cognitive load of remembering which versions of which tools I needed—and where to find them—was becoming overwhelming.

As developers, we automate processes for our users daily, yet we often neglect to apply the same efficiency principles to our own workflows.


2. The Solution: Environment Automation


I decided to create a comprehensive Bash script to automate my entire development environment setup on macOS. This solution leverages the Unix-like foundation of macOS to create a reproducible, consistent environment with a single command.


A. What the Script Does


The script handles a complete development environment setup, including:

  • Core Package Management: Installs and configures Homebrew
  • CLI Tools: Sets up essential command-line utilities (mise, LLVM, compression utilities)
  • Desktop Applications: Installs development applications (IDEs, design tools, communication platforms)
  • Programming Languages: Configures multiple language environments (Node.js, Python, Java, Rust, Go, etc.)
  • Mobile Development: Sets up Android SDK, build tools, and emulators
  • Cross-Platform Tools: Configures cross-compilation for multiple targets
  • Editor Extensions: Enhances Cursor (VS Code-compatible) with productivity plugins
  • Environment Variables: Establishes consistent PATH and tool configurations

The script is designed to be idempotent, meaning it can be run multiple times without causing problems—it only installs what's missing from your system.


B. Why This Approach Works


Beyond the immediate time savings, there are several key benefits to this automated approach:

  1. Reproducibility: New machine? No problem. Run the script and have your exact environment restored.
  2. Documentation as Code: The script serves as living documentation of your development dependencies.
  3. Onboarding: New team members can be productive immediately with consistent environments.
  4. Disaster Recovery: If something breaks, you're just one script execution away from a working environment.


C. Technical Implementation Highlights


The script employs several sophisticated techniques:

  • Intelligent Dependency Checking: Each component is verified before installation attempts
  • Comprehensive Error Handling: Detailed logging and error trapping provides visibility
  • Cross-Platform Support: Special attention to configuring tools that work across development targets
  • Environment Consistency: Careful management of shell configuration and environment variables


D. Customizing for Your Needs


While I've tailored this script for my full-stack development needs, the beauty lies in its adaptability. You can:

  • Remove tools you don't need
  • Add specialized software for your development focus
  • Adjust versions to match your project requirements
  • Extend with additional configurations specific to your workflow


3. The Journey Starts Here


Here is the complete code for my custom script below. However, for improved readability and a more organized structure, I highly recommend visiting my GitHub repository. You'll find it easier to navigate and understand, as I’ve made sure to include thorough documentation and comments for better clarity. Plus, the repository is regularly updated, so you can access the most recent version of the script with all the latest improvements and bug fixes.

#!/usr/bin/env bash

#==============================================================================
#
#  ███╗   ███╗ █████╗  ██████╗ ██████╗ ███████╗    ███████╗███████╗████████╗██╗   ██╗██████╗ 
#  ████╗ ████║██╔══██╗██╔════╝██╔═══██╗██╔════╝    ██╔════╝██╔════╝╚══██╔══╝██║   ██║██╔══██╗
#  ██╔████╔██║███████║██║     ██║   ██║███████╗    ███████╗█████╗     ██║   ██║   ██║██████╔╝
#  ██║╚██╔╝██║██╔══██║██║     ██║   ██║╚════██║    ╚════██║██╔══╝     ██║   ██║   ██║██╔═══╝ 
#  ██║ ╚═╝ ██║██║  ██║╚██████╗╚██████╔╝███████║    ███████║███████╗   ██║   ╚██████╔╝██║     
#  ╚═╝     ╚═╝╚═╝  ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝    ╚══════╝╚══════╝   ╚═╝    ╚═════╝ ╚═╝     
#
#==============================================================================
#
# 🚀 Enterprise macOS Development Environment Setup Script 🚀
#
# Author:         Dimas Yudha Pratama <pingdimasyudha@icloud.com>
# Release Date:   February, 26th 2025
# Version:        1.0.0
#
#==============================================================================
#
# 📋 DESCRIPTION:
# ------------------------------------------------------------------------------
# This production-grade script sets up a complete development environment on 
# macOS arm64 systems. It installs and configures essential development tools,
# SDKs, applications, and environment variables for enterprise development.
#
# The script intelligently checks existing installations before performing
# actions, ensuring idempotence (can be run multiple times safely).
#
# 🔍 FEATURES:
# ------------------------------------------------------------------------------
# ✅ Environment variable & alias configuration
# ✅ Homebrew package & cask installation
# ✅ Development toolkit installation via Mise
# ✅ Rust targets & cargo extensions
# ✅ VS Code extensions via Cursor
# ✅ Android SDK & command line tools
# ✅ Host file configuration
#
# ⚠️ REQUIREMENTS:
# ------------------------------------------------------------------------------
# - macOS with arm64 architecture
# - Administrator privileges (for some operations)
# - Internet connection
#
# 💻 USAGE:
# ------------------------------------------------------------------------------
# 1. Review this script before running
# 2. Execute with: ./setup_script.sh
# 3. You may need to enter your password for sudo operations
#
#==============================================================================

set -euo pipefail
IFS=$'\n\t'

# =============================================================================
# Constants and Configuration
# =============================================================================
readonly CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip"
readonly ANDROID_SDK_VERSION="35"
readonly ANDROID_BUILD_TOOLS_VERSION="35.0.1"
readonly ANDROID_NDK_VERSION="28.0.13004108"

readonly HOST_ENTRIES=(
    "151.101.129.140   i.redditmedia.com"
    "52.34.230.181     www.reddithelp.com"
    "151.101.65.140    g.redditmedia.com"
    "151.101.65.140    a.thumbs.redditmedia.com"
    "151.101.1.140     redditgifts.com"
    "151.101.1.140     i.redd.it"
    "151.101.1.140     old.reddit.com"
    "151.101.1.140     new.reddit.com"
    "151.101.129.140   reddit.com"
    "151.101.129.140   gateway.reddit.com"
    "151.101.129.140   oauth.reddit.com"
    "151.101.129.140   sendbird.reddit.com"
    "151.101.129.140   v.redd.it"
    "151.101.1.140     b.thumbs.redditmedia.com"
    "151.101.1.140     events.reddit.com"
    "54.210.123.98     stats.redditmedia.com"
    "151.101.65.140    www.redditstatic.com"
    "151.101.193.140   www.reddit.com"
    "52.3.23.26        pixel.redditmedia.com"
    "151.101.65.140    www.redditmedia.com"
    "151.101.193.140   about.reddit.com"
    "151.101.1.140     out.reddit.com"
    "107.23.236.34     events.redditmedia.com"
    "151.101.61.140    e.reddit.com"
    "151.101.197.140   s.redditmedia.com"
    "146.75.25.140     gql.reddit.com"
    "151.101.1.140     alb.reddit.com"
    "34.207.103.54     sendbirdproxy-06490ff42851cbcc5.chat.redditmedia.com"
    "52.207.213.188    sendbirdproxy-003d8d1fb8653f6f8.chat.redditmedia.com"
    "34.226.121.89     sendbirdproxy-04ea6c3f71aac3e3f.chat.redditmedia.com"
)

declare -A ENV_VARS=(
    ["ANDROID_HOME"]="$HOME/Library/Android/sdk"
    ["CHROME_EXECUTABLE"]="/Applications/Arc.app/Contents/MacOS/Arc"
    ["GRADLE_HOME"]="$HOME/.local/share/mise/installs/gradle/8.12.1"
    ["JAVA_HOME"]="$HOME/.local/share/mise/installs/java/graalvm-community-21.0.2"
    ["NDK_HOME"]='$ANDROID_HOME/ndk/$(ls -1 $ANDROID_HOME/ndk)'
    ["PATH"]='$HOME/.local/share/mise/shims:$GRADLE_HOME/bin:$PATH'
    ["VISUAL"]="nano"
    ["EDITOR"]="nano"
)

declare -A ALIASES=(
    ["brew-upgrade"]="brew cleanup --prune=all && brew upgrade --formula && brew upgrade --cask --greedy-latest --greedy-auto-updates"
)

readonly BREW_PACKAGES=(
    "mise"
    "openconnect"
    "nsis"
    "llvm"
    "rar"
    "zip"
    "unzip"
)

readonly BREW_CASKS=(
    "cursor"
    "android-studio"
    "figma"
    "dbeaver-enterprise"
    "warp"
    "intellij-idea"
    "raycast"
    "arc"
    "podman-desktop"
    "insomnia"
    "microsoft-office"
    "whatsapp"
    "slack"
    "zoom"
    "termius"
    "discord"
    "jetbrains-toolbox"
)

readonly MISE_PACKAGES=(
    "awscli@2.24.10"
    "azure-cli@2.69.0"
    "bun@1.2.3"
    "cocoapods@1.16.2"
    "flutter@3.29.0-stable"
    "gcloud@511.0.0"
    "github-cli@2.67.0"
    "glab@1.53.0"
    "go@1.24.0"
    "gradle@8.12.1"
    "helm@3.17.1"
    "java@corretto-11.0.26.4.1"
    "java@corretto-8.442.06.1"
    "java@graalvm-community-21.0.2"
    "jq@1.7.1"
    "kotlin@2.1.10"
    "kubectl@1.32.2"
    "maven@3.9.9"
    "minikube@1.35.0"
    "node@22.14.0"
    "pnpm@10.4.1"
    "python@2.7.18"
    "python@3.13.2"
    "rclone@1.69.1"
    "rust@1.85.0"
    "snyk@1.1260.0"
    "terraform@1.10.5"
    "yarn@4.6.0"
    "zig@0.13.0"
)

readonly RUSTUP_TARGETS=(
    "aarch64-linux-android"
    "armv7-linux-androideabi"
    "i686-linux-android"
    "x86_64-linux-android"
    "aarch64-apple-ios"
    "x86_64-apple-ios"
    "aarch64-apple-ios-sim"
    "x86_64-pc-windows-msvc"
    "i686-pc-windows-msvc"
    "aarch64-pc-windows-msvc"
    "aarch64-apple-darwin"
)

readonly VSCODE_EXTENSIONS=(
    "donjayamanne.python-extension-pack"
    "ms-vscode.cpptools-extension-pack"
    "vscjava.vscode-java-pack"
    "jawandarajbir.react-vscode-extension-pack"
    "loiane.angular-extension-pack"
    "swellaby.rust-pack"
    "Dart-Code.flutter"
    "oven.bun-vscode"
    "circleci.circleci"
    "SonarSource.sonarlint-vscode"
    "esbenp.prettier-vscode"
    "dbaeumer.vscode-eslint"
    "tauri-apps.tauri-vscode"
    "expo.vscode-expo-tools"
    "snyk-security.snyk-vulnerability-scanner"
    "richardwillis.vscode-gradle-extension-pack"
    "redhat.vscode-quarkus"
    "ms-azuretools.vscode-docker"
    "ms-kubernetes-tools.vscode-kubernetes-tools"
    "usernamehw.errorlens"
    "aaron-bond.better-comments"
    "EditorConfig.EditorConfig"
    "ionic.ionic"
    "JarredSumner.zig-unofficial"
    "Equinusocio.vsc-material-theme"
    "Equinusocio.vsc-material-theme-icons"
    "golang.Go"
    "HashiCorp.terraform"
    "vscjava.vscode-lombok"
    "vmware.vscode-boot-dev-pack"
    # "Vue.volar"
    # "astro-build.astro-vscode"
    "bradlc.vscode-tailwindcss"
)

readonly ANDROID_SDK_COMPONENTS=(
    "build-tools;${ANDROID_BUILD_TOOLS_VERSION}"
    "emulator"
    "ndk;${ANDROID_NDK_VERSION}"
    "platform-tools"
    "platforms;android-${ANDROID_SDK_VERSION}"
    "sources;android-${ANDROID_SDK_VERSION}"
    "system-images;android-${ANDROID_SDK_VERSION};default;arm64-v8a"
)

# =============================================================================
# Logging and Error Handling Functions
# =============================================================================

#==============================================================================
# Function: 📝 log_message
#==============================================================================
# 
# Logs a message with beautiful formatting and timestamps to provide a 
# consistent and visually appealing interface for tracking script progress.
# 
# The function applies different color coding based on the message level:
# - INFO: 🔵 Blue - General information messages
# - WARN: 🟡 Yellow - Warning messages that need attention but aren't fatal
# - SUCCESS: 🟢 Green - Successful completion notifications
# - ERROR: 🔴 Red - Critical errors that may affect operation
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   $1  - Level   : The log level (INFO|WARN|SUCCESS|ERROR)
#   $2  - Message : The actual message content to be displayed
#
# RETURNS:
#   None - Output is sent to stdout with appropriate formatting
#
# EXAMPLES:
#   log_message "INFO" "Beginning installation of Homebrew packages..."
#   log_message "SUCCESS" "✅ All components installed successfully!"
#   log_message "ERROR" "Failed to connect to server: Connection refused"
#------------------------------------------------------------------------------
log_message() {
    local level=$1
    local message=$2
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    case $level in
        INFO)
            echo -e "\033[1;34m[INFO]\033[0m [$timestamp] 🔵 $message"
            ;;
        WARN)
            echo -e "\033[1;33m[WARN]\033[0m [$timestamp] ⚠️  $message"
            ;;
        SUCCESS)
            echo -e "\033[1;32m[SUCCESS]\033[0m [$timestamp] ✅ $message"
            ;;
        ERROR)
            echo -e "\033[1;31m[ERROR]\033[0m [$timestamp] ❌ $message"
            ;;
        *)
            echo -e "[$level] [$timestamp] $message"
            ;;
    esac
}

#==============================================================================
# Function: 🚫 handle_error
#==============================================================================
# 
# Sophisticated error handler that traps unexpected failures during script 
# execution. This function provides detailed diagnostic information when
# something goes wrong, allowing for easier troubleshooting.
#
# When an error occurs, the function will:
# 1. Display the error with line number and exit code
# 2. Terminate the script with the appropriate exit code
# 3. Ensure the user understands where the problem occurred
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   $1  - Exit Code   : The exit status of the failed command
#   $2  - Line Number : The line number where the error occurred
#
# RETURNS:
#   None - The script will exit with the provided exit code
#
# EXAMPLES:
#   # Set up the error trap at the beginning of your script:
#   trap 'handle_error $? $LINENO' ERR
#------------------------------------------------------------------------------
handle_error() {
    local exit_code=$1
    local line_number=$2
    
    log_message "ERROR" "🚨 Script execution failed at line $line_number with exit code $exit_code"
    log_message "ERROR" "Please check the error details above for more information"
    exit $exit_code
}

# Set error trap to catch and handle failures
trap 'handle_error $? $LINENO' ERR

# =============================================================================
# Utility Functions
# =============================================================================

#==============================================================================
# Function: 🔍 check_command
#==============================================================================
# 
# Elegant utility that verifies if a command exists in the system PATH.
# This function provides a clean way to check for command availability
# before attempting to use it, preventing cryptic errors.
#
# The function uses the shell's built-in command checking capability
# to determine if the specified command is available for execution.
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   $1  - Command : The name of the command to check for
#
# RETURNS:
#   0 - If the command exists and is executable
#   1 - If the command does not exist or cannot be executed
#
# EXAMPLES:
#   if check_command "brew"; then
#       echo "Homebrew is available"
#   else
#       echo "Homebrew needs to be installed"
#   fi
#
#   # Check for multiple commands in sequence
#   for cmd in git npm node python; do
#       check_command "$cmd" || log_message "WARN" "$cmd is not installed"
#   done
#------------------------------------------------------------------------------
check_command() {
    command -v "$1" &> /dev/null
    return $?
}

#==============================================================================
# Function: 🖥️  check_os_and_arch
#==============================================================================
# 
# Performs a critical verification that ensures the script is running on
# the correct operating system (macOS) and architecture (arm64). This 
# safety check prevents unexpected behavior on unsupported platforms.
#
# The function checks both the operating system type and CPU architecture
# simultaneously, providing clear feedback if either check fails.
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None
#
# RETURNS:
#   0 - If running on macOS with arm64 architecture
#   1 - If running on any other OS or architecture
#
# EXAMPLES:
#   # Validate platform before proceeding
#   if check_os_and_arch; then
#       log_message "INFO" "Platform check passed, continuing installation"
#   else
#       log_message "ERROR" "This script must run on macOS with Apple Silicon"
#       exit 1
#   fi
#------------------------------------------------------------------------------
check_os_and_arch() {
    if [[ "$(uname -s)" != "Darwin" || "$(uname -m)" != "arm64" ]]; then
        log_message "ERROR" "❌ Platform check failed! This script requires macOS with arm64 architecture."
        log_message "ERROR" "Current OS: $(uname -s), Architecture: $(uname -m)"
        return 1
    fi
    
    log_message "INFO" "✓ Platform verification: macOS arm64 ✓"
    return 0
}

# =============================================================================
# Installation Functions
# =============================================================================

#==============================================================================
# Function: ⚙️  configure_environment_variables
#==============================================================================
# 
# Meticulously configures your shell environment by setting up essential
# environment variables and useful aliases in your ~/.zshrc file.
#
# This function performs intelligent management of your environment:
# 1. Creates ~/.zshrc if it doesn't exist
# 2. Checks for existing variable definitions to prevent duplication
# 3. Special handling for PATH to maintain existing entries
# 4. Adds convenient aliases for common operations
# 5. Only makes changes when necessary (idempotent operation)
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None - Uses predefined ENV_VARS and ALIASES associative arrays
#
# RETURNS:
#   0 - Always returns success, with appropriate logging
#
# ENVIRONMENT VARIABLES SET:
#   - ANDROID_HOME - Location of Android SDK
#   - CHROME_EXECUTABLE - Path to Chrome/Arc browser
#   - GRADLE_HOME - Gradle installation directory
#   - JAVA_HOME - Java installation directory
#   - NDK_HOME - Android NDK location
#   - PATH - Extended with development tool paths
#   - VISUAL/EDITOR - Default text editors
#
# ALIASES CREATED:
#   - brew-upgrade - One-command Homebrew maintenance
#   - dir - Enhanced directory listing
#------------------------------------------------------------------------------
configure_environment_variables() {
    log_message "INFO" "🛠️  Setting up environment variables in ~/.zshrc"
    local zshrc="$HOME/.zshrc"
    local modified=0
    
    if [[ ! -f "$zshrc" ]]; then
        touch "$zshrc"
        log_message "INFO" "📄 Created new ~/.zshrc file"
    fi
    
    # Process environment variables
    for var_name in "${!ENV_VARS[@]}"; do
        local var_value="${ENV_VARS[$var_name]}"
        
        # Check if variable is already set
        if ! grep -q "^export ${var_name}=" "$zshrc"; then
            # Special handling for PATH variable
            if [[ "$var_name" == "PATH" ]]; then
                if grep -q "export PATH=" "$zshrc"; then
                    # PATH exists, check for missing components
                    local path_components=("$HOME/.local/share/mise/shims" "$GRADLE_HOME/bin")
                    local missing_components=()
                    
                    for component in "${path_components[@]}"; do
                        if ! grep -q "PATH=.*${component}" "$zshrc"; then
                            missing_components+=("$component")
                        fi
                    done
                    
                    if [[ ${#missing_components[@]} -gt 0 ]]; then
                        # Create new PATH with missing components
                        local path_line=$(grep "export PATH=" "$zshrc")
                        local new_path="export PATH="
                        for component in "${missing_components[@]}"; do
                            new_path+="$component:"
                        done
                        new_path+="${path_line#export PATH=}"
                        
                        # Update PATH line
                        sed -i.bak "s|export PATH=.*|${new_path}|" "$zshrc"
                        log_message "INFO" "🔄 Updated PATH variable with missing components"
                        modified=1
                    fi
                else
                    # PATH doesn't exist, add it
                    echo "export PATH=${var_value}" >> "$zshrc"
                    log_message "INFO" "➕ Added PATH variable"
                    modified=1
                fi
            else
                # Standard variable export
                echo "export ${var_name}=${var_value}" >> "$zshrc"
                log_message "INFO" "➕ Added ${var_name} variable"
                modified=1
            fi
        else
            log_message "INFO" "✓ ${var_name} is already properly configured"
        fi
    done
    
    # Process aliases
    for alias_name in "${!ALIASES[@]}"; do
        local alias_value="${ALIASES[$alias_name]}"
        
        if ! grep -q "^alias ${alias_name}=" "$zshrc"; then
            echo "alias ${alias_name}=\"${alias_value}\"" >> "$zshrc"
            log_message "INFO" "➕ Added alias ${alias_name}"
            modified=1
        else
            log_message "INFO" "✓ Alias ${alias_name} is already configured"
        fi
    done
    
    if [[ $modified -eq 1 ]]; then
        log_message "SUCCESS" "🎉 Environment variables and aliases have been updated"
        log_message "INFO" "💡 Run 'source ~/.zshrc' to apply changes to current session"
    else
        log_message "INFO" "✓ Shell environment is already properly configured"
    fi
    
    return 0
}

#==============================================================================
# Function: 🍺 install_homebrew
#==============================================================================
# 
# Sets up Homebrew, the essential package manager for macOS development.
# This foundational tool enables installation of numerous development
# tools, utilities, and applications needed in your workflow.
#
# The function performs these operations:
# 1. Checks if Homebrew is already installed to avoid duplication
# 2. Downloads and runs the official Homebrew installation script
# 3. Configures shell integration for immediate use
# 4. Verifies successful installation
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None
#
# RETURNS:
#   0 - Always returns success, with appropriate logging
#
# SIDE EFFECTS:
#   - Downloads and installs Homebrew if not present
#   - Modifies ~/.zprofile to include Homebrew in PATH
#   - Runs brew shellenv to configure current session
#
# EXAMPLES:
#   install_homebrew
#   if check_command "brew"; then
#     echo "Homebrew is ready to use"
#   fi
#------------------------------------------------------------------------------
install_homebrew() {
    if ! check_command "brew"; then
        log_message "INFO" "🍺 Installing Homebrew package manager..."
        /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

        echo >> "$HOME/.zprofile"
        echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> "$HOME/.zprofile"
        eval "$(/opt/homebrew/bin/brew shellenv)"

        log_message "SUCCESS" "🎉 Homebrew installed successfully and ready to use"
    else
        log_message "INFO" "✓ Homebrew is already installed"
    fi

    return 0
}

#==============================================================================
# Function: 📦 install_brew_packages
#==============================================================================
# 
# Intelligently installs essential command-line utilities and development
# tools via Homebrew. This function ensures your system has the core
# utilities required for a productive development environment.
#
# The function features:
# 1. Smart detection of already installed packages to avoid reinstallation
# 2. Batch installation of missing packages for efficiency
# 3. Detailed progress reporting and validation
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None - Uses predefined BREW_PACKAGES array
#
# RETURNS:
#   0 - Always returns success, with appropriate logging
#
# PACKAGES INSTALLED:
#   - mise: Runtime version manager (for languages & tools)
#   - openconnect: VPN client compatible with Cisco AnyConnect
#   - nsis: Installer creation system for Windows
#   - llvm: Compiler infrastructure
#   - rar/zip/unzip: Archive utilities
#   - and others defined in BREW_PACKAGES
#
# EXAMPLES:
#   install_brew_packages
#------------------------------------------------------------------------------
install_brew_packages() {
    log_message "INFO" "📦 Installing essential Homebrew packages..."

    local packages_to_install=()

    for package in "${BREW_PACKAGES[@]}"; do
        if ! brew list "$package" &>/dev/null; then
            packages_to_install+=("$package")
        else
            log_message "INFO" "✓ Package '$package' is already installed"
        fi
    done

    if [[ ${#packages_to_install[@]} -gt 0 ]]; then
        log_message "INFO" "🔄 Installing packages: ${packages_to_install[*]}"
        brew install "${packages_to_install[@]}"
        log_message "SUCCESS" "🎉 Homebrew packages installed successfully"
    else
        log_message "INFO" "✓ All required Homebrew packages are already installed"
    fi

    return 0
}

#==============================================================================
# Function: 🖥️  install_brew_casks
#==============================================================================
# 
# Provisions your macOS system with essential desktop applications for
# development, productivity, and communication. This function uses Homebrew
# Cask to install GUI applications with a single command.
#
# The function intelligently:
# 1. Identifies which applications are already installed
# 2. Batch installs only missing applications
# 3. Provides visual progress reporting with status checks
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None - Uses predefined BREW_CASKS array
#
# RETURNS:
#   0 - Always returns success, with appropriate logging
#
# APPLICATIONS INSTALLED:
#   - Development: Cursor, Android Studio, IntelliJ IDEA, VS Code
#   - Utilities: Raycast, Arc browser, Podman Desktop
#   - Design: Figma
#   - Database: DBeaver Enterprise
#   - Communication: Slack, WhatsApp, Zoom, Discord
#   - And others defined in BREW_CASKS
#
# EXAMPLES:
#   install_brew_casks
#------------------------------------------------------------------------------
install_brew_casks() {
    log_message "INFO" "🖥️  Installing desktop applications via Homebrew Cask..."

    local casks_to_install=()

    for cask in "${BREW_CASKS[@]}"; do
        if ! brew list --cask "$cask" &>/dev/null 2>&1; then
            casks_to_install+=("$cask")
        else
            log_message "INFO" "✓ Application '$cask' is already installed"
        fi
    done

    if [[ ${#casks_to_install[@]} -gt 0 ]]; then
        log_message "INFO" "🔄 Installing applications: ${casks_to_install[*]}"
        brew install --cask "${casks_to_install[@]}"
        log_message "SUCCESS" "🎉 Applications installed successfully"
    else
        log_message "INFO" "✓ All required applications are already installed"
    fi

    return 0
}

#==============================================================================
# Function: 🛠️  install_mise_packages
#==============================================================================
# 
# Provisions your development environment with a comprehensive suite of
# programming languages, SDKs, and developer tools using the mise version
# manager. This ensures you have the exact versions needed for your projects.
#
# The function provides:
# 1. Intelligent version management for multiple development tools
# 2. Installation of only missing SDKs and tools
# 3. Global version configuration for immediate use
# 4. Detailed progress and validation for each component
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None - Uses predefined MISE_PACKAGES array
#
# RETURNS:
#   0 - On success
#   1 - If mise is not available
#
# TOOLS INSTALLED:
#   - Languages: Python, Node.js, Java, Go, Rust, Kotlin, Zig
#   - Cloud tools: AWS CLI, Azure CLI, Google Cloud SDK, Terraform
#   - Mobile: Flutter, CocoaPods
#   - Build tools: Gradle, Maven
#   - And many others defined in MISE_PACKAGES
#
# EXAMPLES:
#   install_mise_packages
#------------------------------------------------------------------------------
install_mise_packages() {
    log_message "INFO" "🛠️  Setting up development tools via Mise version manager..."

    if ! check_command "mise"; then
        log_message "ERROR" "❌ Mise not found. Ensure Homebrew installation succeeded."
        return 1
    fi

    for package in "${MISE_PACKAGES[@]}"; do
        local package_name=$(echo "$package" | cut -d '@' -f 1)
        local package_version=$(echo "$package" | cut -d '@' -f 2)

        if ! mise list "$package_name" 2>/dev/null | grep -q "$package_version"; then
            log_message "INFO" "📥 Installing $package_name v$package_version..."
            mise install "$package"
            mise use -g "$package"
            log_message "SUCCESS" "✅ Installed $package_name v$package_version"
        else
            log_message "INFO" "✓ $package_name v$package_version is already installed"
        fi
    done

    log_message "SUCCESS" "🎉 All development toolkits are now available"
    return 0
}

#==============================================================================
# Function: 🔧 install_cargo_xwin
#==============================================================================
# 
# Installs the cargo-xwin utility for Rust cross-compilation to Windows
# platforms. This specialized tool enables building Windows executables
# from your macOS environment without needing a Windows machine.
#
# The function:
# 1. Verifies cargo (Rust's package manager) is available
# 2. Checks if cargo-xwin is already installed
# 3. Performs a locked installation to ensure compatibility
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None
#
# RETURNS:
#   0 - On success
#   1 - If cargo is not available
#
# SIDE EFFECTS:
#   - Installs cargo-xwin with the --locked flag for version stability
#
# EXAMPLES:
#   install_cargo_xwin
#------------------------------------------------------------------------------
install_cargo_xwin() {
    log_message "INFO" "🔧 Checking for Rust Windows cross-compilation tools..."

    if ! check_command "cargo"; then
        log_message "ERROR" "❌ Cargo not found. Ensure Rust installation succeeded."
        return 1
    fi

    if ! cargo install --list | grep -q "cargo-xwin"; then
        log_message "INFO" "📥 Installing cargo-xwin for Windows cross-compilation..."
        cargo install --locked cargo-xwin
        log_message "SUCCESS" "✅ Installed cargo-xwin successfully"
    else
        log_message "INFO" "✓ cargo-xwin is already installed"
    fi

    return 0
}

#==============================================================================
# Function: 🎯 install_rustup_targets
#==============================================================================
# 
# Configures Rust for cross-platform development by installing multiple
# target platforms. This enables compiling Rust code for various operating
# systems and architectures from your macOS environment.
#
# The function enables compilation for:
# 1. Android (arm64, armv7, x86, x86_64)
# 2. iOS (arm64, x86_64, simulator)
# 3. Windows (x86, x86_64, arm64)
# 4. macOS (arm64)
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None - Uses predefined RUSTUP_TARGETS array
#
# RETURNS:
#   0 - On success
#   1 - If rustup is not available
#
# PLATFORMS ENABLED:
#   - Android: aarch64, armv7, i686, x86_64
#   - iOS: arm64, x86_64, simulator
#   - Windows (MSVC): x86, x86_64, arm64
#   - macOS: arm64
#
# EXAMPLES:
#   install_rustup_targets
#------------------------------------------------------------------------------
install_rustup_targets() {
    log_message "INFO" "🎯 Configuring Rust for cross-platform development..."

    if ! check_command "rustup"; then
        log_message "ERROR" "❌ Rustup not found. Ensure Rust installation succeeded."
        return 1
    fi

    local installed_targets=$(rustup target list --installed)
    local targets_to_install=()

    for target in "${RUSTUP_TARGETS[@]}"; do
        if ! echo "$installed_targets" | grep -q "$target"; then
            targets_to_install+=("$target")
        else
            log_message "INFO" "✓ Rust target '$target' is already configured"
        fi
    done

    if [[ ${#targets_to_install[@]} -gt 0 ]]; then
        log_message "INFO" "🔄 Installing Rust compilation targets:"
        for target in "${targets_to_install[@]}"; do
            log_message "INFO" "  → Adding $target..."
            rustup target add "$target"
            log_message "INFO" "  ✓ Added $target"
        done
        log_message "SUCCESS" "✅ Rust cross-compilation targets installed"
    else
        log_message "INFO" "✓ All required Rust targets are already configured"
    fi

    return 0
}

#==============================================================================
# Function: 🧩 install_vscode_extensions
#==============================================================================
# 
# Enhances your Cursor (VS Code-compatible) editor with a comprehensive
# suite of extensions for various programming languages, frameworks, and
# development workflows.
#
# This function:
# 1. Verifies if Cursor is installed
# 2. Intelligently detects already installed extensions
# 3. Installs only missing extensions
# 4. Provides detailed progress reporting
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None - Uses predefined VSCODE_EXTENSIONS array
#
# RETURNS:
#   0 - On success
#   1 - If Cursor is not available
#
# EXTENSION CATEGORIES:
#   - Language packs: Python, Java, C++, Rust, Flutter/Dart
#   - Framework support: React, Angular, Vue, Tauri, Astro
#   - DevOps: Docker, Kubernetes, CircleCI
#   - Tools: ESLint, Prettier, Error Lens, SonarLint
#   - Themes: Material Theme with icons
#
# EXAMPLES:
#   install_vscode_extensions
#------------------------------------------------------------------------------
install_vscode_extensions() {
    log_message "INFO" "🧩 Enhancing Cursor with powerful development extensions..."

    if ! check_command "cursor"; then
        log_message "ERROR" "❌ Cursor not found. Ensure it was installed via Homebrew Cask."
        return 1
    fi

    local installed_extensions=$(cursor --list-extensions 2>/dev/null)
    local extensions_to_install=()

    for extension in "${VSCODE_EXTENSIONS[@]}"; do
        if ! echo "$installed_extensions" | grep -q -i "$extension"; then
            extensions_to_install+=("$extension")
        else
            log_message "INFO" "✓ Extension '$extension' is already installed"
        fi
    done

    if [[ ${#extensions_to_install[@]} -gt 0 ]]; then
        log_message "INFO" "🔄 Installing VS Code extensions..."
        for extension in "${extensions_to_install[@]}"; do
            log_message "INFO" "  → Installing $extension..."
            cursor --install-extension "$extension"
            log_message "INFO" "  ✓ Installed $extension"
        done
        log_message "SUCCESS" "✅ All Cursor extensions installed successfully"
    else
        log_message "INFO" "✓ All required Cursor extensions are already installed"
    fi

    return 0
}

#==============================================================================
# Function: 📱 setup_android_sdk
#==============================================================================
# 
# Establishes a complete Android development environment by installing and
# configuring the Android SDK, command-line tools, build tools, platform
# SDKs, and emulator. Essential for mobile app development.
#
# This comprehensive function:
# 1. Creates the Android SDK directory structure
# 2. Downloads and installs Android command-line tools
# 3. Automatically accepts SDK licenses (required for CI/CD)
# 4. Installs specific Android API levels, build tools, and NDK
# 5. Configures system images for the Android emulator
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None
#
# RETURNS:
#   0 - On success
#
# COMPONENTS INSTALLED:
#   - Android command-line tools
#   - Android SDK Platform API ${ANDROID_SDK_VERSION}
#   - Android Build Tools ${ANDROID_BUILD_TOOLS_VERSION}
#   - Android NDK ${ANDROID_NDK_VERSION}
#   - Android Emulator with arm64-v8a system image
#   - Android platform tools (adb, fastboot)
#
# EXAMPLES:
#   setup_android_sdk
#------------------------------------------------------------------------------
setup_android_sdk() {
    log_message "INFO" "📱 Setting up complete Android development environment..."

    local android_home="${ENV_VARS[ANDROID_HOME]}"

    mkdir -p "$android_home"

    if [[ ! -d "$android_home/cmdline-tools/latest" ]]; then
        log_message "INFO" "📥 Downloading Android command-line tools..."

        local temp_dir=$(mktemp -d)
        local zip_file="$temp_dir/cmdline-tools.zip"

        curl -L -o "$zip_file" "$CMDLINE_TOOLS_URL"

        mkdir -p "$android_home/cmdline-tools"

        log_message "INFO" "📦 Extracting Android command-line tools..."
        unzip -q "$zip_file" -d "$temp_dir"
        mv "$temp_dir/cmdline-tools" "$android_home/cmdline-tools/latest"

        rm -rf "$temp_dir"

        log_message "SUCCESS" "✅ Android command-line tools installed"
    else
        log_message "INFO" "✓ Android command-line tools are already installed"
    fi

    local sdkmanager="$android_home/cmdline-tools/latest/bin/sdkmanager"

    if [[ ! -d "$android_home/licenses" ]] || [[ $(find "$android_home/licenses" -type f | wc -l) -eq 0 ]]; then
        log_message "INFO" "📜 Accepting Android SDK licenses (required for CI/CD)..."
        yes | "$sdkmanager" --licenses
        log_message "SUCCESS" "✅ Android SDK licenses accepted"
    else
        log_message "INFO" "✓ Android SDK licenses are already accepted"
    fi

    log_message "INFO" "🧩 Installing Android SDK components..."
    local components_to_install=()

    for component in "${ANDROID_SDK_COMPONENTS[@]}"; do
        if ! "$sdkmanager" --list | grep -q "$component"; then
            components_to_install+=("$component")
        else
            log_message "INFO" "✓ Android SDK component '$component' is already installed"
        fi
    done

    if [[ ${#components_to_install[@]} -gt 0 ]]; then
        log_message "INFO" "🔄 Installing Android SDK components: ${components_to_install[*]}"
        "$sdkmanager" --install "${components_to_install[@]}"
        log_message "SUCCESS" "✅ Android SDK components installed successfully"
    else
        log_message "INFO" "✓ All required Android SDK components are already installed"
    fi

    return 0
}

#==============================================================================
# Function: 🌐 add_hosts_entries
#==============================================================================
# 
# Enhances your networking configuration by adding essential host entries
# to your system's /etc/hosts file. This improves connectivity to specific
# services and may bypass certain network restrictions.
#
# This function:
# 1. Checks if entries already exist to avoid duplication
# 2. Creates a temporary file for safe modification
# 3. Uses sudo only when necessary to update system files
# 4. Provides detailed logging of changes
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None - Uses predefined HOST_ENTRIES array
#
# RETURNS:
#   0 - Always returns success
#
# HOSTS CONFIGURED:
#   - i.redditmedia.com → 151.101.129.140
#   - www.reddithelp.com → 52.34.230.181
#   - And others defined in HOST_ENTRIES
#
# EXAMPLES:
#   add_hosts_entries
#------------------------------------------------------------------------------
add_hosts_entries() {
    log_message "INFO" "🌐 Optimizing host resolution in system configuration..."

    local hosts_file="/etc/hosts"
    local temp_hosts=$(mktemp)
    local modified=0

    cat "$hosts_file" > "$temp_hosts"

    for entry in "${HOST_ENTRIES[@]}"; do
        if ! grep -q "^$entry$" "$hosts_file"; then
            echo "$entry" >> "$temp_hosts"
            log_message "INFO" "➕ Adding host entry: $entry"
            modified=1
        else
            log_message "INFO" "✓ Host entry already exists: $entry"
        fi
    done

    if [[ $modified -eq 1 ]]; then
        log_message "INFO" "🔐 Updating system hosts file (sudo password required)..."
        sudo cp "$temp_hosts" "$hosts_file"
        log_message "SUCCESS" "✅ Network host configuration updated successfully"
    else
        log_message "INFO" "✓ Network host configuration is already properly set up"
    fi

    rm -rf "$temp_hosts"

    return 0
}

# =============================================================================
# Main Script Execution
# =============================================================================

#==============================================================================
# Function: 🚀 main
#==============================================================================
# 
# Orchestrates the complete setup process by calling individual functions
# in the correct sequence. This is the entry point that coordinates the
# entire installation and configuration workflow.
#
# The sequence ensures dependencies are installed before they're needed:
# 1. Verify system compatibility (OS and architecture)
# 2. Install package managers and core utilities
# 3. Install development tools and SDKs
# 4. Configure system settings and environment
#
#------------------------------------------------------------------------------
# PARAMETERS:
#   None
#
# RETURNS:
#   0 - On successful completion
#   Non-zero - If any critical step fails
#
# EXAMPLES:
#   main
#------------------------------------------------------------------------------
main() {
    log_message "INFO" "🚀 Starting developer environment setup for macOS arm64"

    check_os_and_arch || exit 1
    install_homebrew
    install_brew_packages
    install_brew_casks
    setup_android_sdk
    configure_environment_variables
    install_mise_packages
    install_cargo_xwin
    install_rustup_targets
    install_vscode_extensions
    add_hosts_entries

    log_message "SUCCESS" "🎉 Development environment setup completed successfully!"
    log_message "INFO" "💡 Please restart your terminal or run 'source ~/.zshrc' to apply changes"

    return 0
}

main        

4. What's Next


I'm continuing to enhance this script with additional features.

Currently, I'm exploring better ways to configure iOS development (Xcode, simulators) and improve support for game development on macOS.

The script is available on my GitHub repository under an MIT license. Pull requests and suggestions are welcome!

By investing a few hours in automation, I've saved countless hours of setup time and eliminated the cognitive burden of remembering complex installation procedures. As developers, perhaps our most valuable skill isn't just writing code for others, but knowing when to write code for ourselves.

What automation strategies have you implemented in your development workflow? I'd love to hear your approaches in the comments.


#DeveloperProductivity #AutomationTools #DevOps #MacOSDevelopment #FullStackDevelopment #DeveloperWorkflow #BashScripting #DeveloperTools #CrossPlatformDevelopment #TechAutomation

Dimas, this approach to automating development setups is revolutionary! By simplifying configurations with a single Bash script, you’re turning a traditionally laborious task into a streamlined process. I’m curious, have you faced any unique challenges or limitations while refining this script for different development environments? Such insights could be incredibly valuable to the community!

Like
Reply

To view or add a comment, sign in

More articles by Dimas Yudha Pratama

Others also viewed

Explore content categories