#!/usr/bin/env bash # ======================================================= # 1. BOOTSTRAPPER: PREFER ZSH, FALLBACK TO BASH # ======================================================= if [ -z "${_PREFER_ZSH_BOOTSTRAPPED:-}" ]; then export _PREFER_ZSH_BOOTSTRAPPED=1 # Only attempt to re-execute if $0 is an actual file on disk. # This prevents the 'can't open input file' error when running # from memory via `bash -c "$(curl ...)"` or `zsh -c`. if [ -f "$0" ]; then if command -v zsh >/dev/null 2>&1; then exec zsh "$0" "$@" elif command -v bash >/dev/null 2>&1; then exec bash "$0" "$@" else echo "Error: Neither Zsh nor Bash is installed. Exiting." exit 1 fi fi fi set -u # ======================================================= # 2. CROSS-SHELL NORMALIZATION # ======================================================= if [ -n "${ZSH_VERSION:-}" ]; then # Zsh: Enable Bash-like word splitting for unquoted variables (menu input) setopt shwordsplit 2>/dev/null || true CURRENT_SHELL="zsh" PROFILE_FILE="$HOME/.zshrc" elif [ -n "${BASH_VERSION:-}" ]; then # Bash CURRENT_SHELL="bash" PROFILE_FILE="$HOME/.bashrc" else # Fallback POSIX CURRENT_SHELL="sh" PROFILE_FILE="$HOME/.profile" fi # ======================================================= # COLORS & LOGGING # ======================================================= GREEN="\033[0;32m" RED="\033[0;31m" YELLOW="\033[1;33m" BLUE="\033[0;34m" NC="\033[0m" log() { printf "${GREEN}▶ %s${NC}\n" "$*"; } warn() { printf "${YELLOW}⚠ %s${NC}\n" "$*"; } err() { printf "${RED}✖ %s${NC}\n" "$*"; } info() { printf "${BLUE}ℹ %s${NC}\n" "$*"; } # ======================================================= # HELPERS & SYSTEM DETECTION # ======================================================= require_cmd() { command -v "$1" >/dev/null 2>&1; } add_to_path_config() { local label=$1 local path_line=$2 local target_file="${3:-$PROFILE_FILE}" if [[ -f "$target_file" ]]; then if ! grep -q "$label" "$target_file"; then printf "\n# %s\n%s\n" "$label" "$path_line" >> "$target_file" log "Added $label to $target_file" fi else printf "\n# %s\n%s\n" "$label" "$path_line" >> "$PROFILE_FILE" log "Added $label to shell profile ($PROFILE_FILE)." fi # Instantly evaluate the path line so the current script session # can immediately use the newly installed tool in subsequent steps. eval "$path_line" 2>/dev/null || true } detect_os_arch() { ARCH=$(uname -m) case "$ARCH" in x86_64|amd64) SYS_ARCH="amd64"; MAC_ARCH="x86_64"; AWS_ARCH="x86_64" ;; aarch64|arm64) SYS_ARCH="arm64"; MAC_ARCH="arm64"; AWS_ARCH="aarch64" ;; *) err "Unsupported architecture: $ARCH"; exit 1 ;; esac if [[ "$OSTYPE" == "darwin"* ]]; then OS="macOS" OS_LOWER="darwin" elif grep -qi microsoft /proc/version 2>/dev/null; then OS="WSL" OS_LOWER="linux" elif [[ -f /etc/os-release ]]; then . /etc/os-release [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* ]] && OS="Ubuntu" || OS="Linux" OS_LOWER="linux" else OS="Unknown" OS_LOWER="unknown" fi log "Detected: $OS ($ARCH) running $CURRENT_SHELL" } detect_os_arch # ======================================================= # CORE RUNTIMES & MANAGERS # ======================================================= install_build_tools() { log "Installing Build Essentials & Core Dependencies..." case "$OS" in Ubuntu|WSL) sudo apt-get update && sudo apt-get install -y build-essential curl wget git jq unzip libssl-dev zlib1g-dev libffi-dev libsqlite3-dev ;; macOS) xcode-select --install || warn "Xcode tools already installed" ;; *) warn "Manual installation required for $OS." ;; esac } install_brew() { if require_cmd brew; then warn "Homebrew already installed"; return; fi log "Installing Homebrew from Official Source..." # Homebrew's installer specifically requires Bash execution, regardless of current shell /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" if [[ "$OS_LOWER" == "linux" ]]; then add_to_path_config "HOMEBREW" 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' elif [[ "$MAC_ARCH" == "arm64" ]]; then add_to_path_config "HOMEBREW" 'eval "$(/opt/homebrew/bin/brew shellenv)"' else add_to_path_config "HOMEBREW" 'eval "$(/usr/local/bin/brew shellenv)"' fi } install_nvm() { export NVM_DIR="$HOME/.nvm" if [ -d "$NVM_DIR" ]; then warn "NVM is already installed." else log "Installing NVM via $CURRENT_SHELL..." curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | $CURRENT_SHELL fi [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" printf "\n${YELLOW}Which version of Node.js would you like to install?${NC}\n" printf " 1) LTS (Long Term Support - Recommended for stability)\n" printf " 2) Latest (Current features - Recommended for testing new APIs)\n" printf " 3) Skip installing Node.js right now\n" printf "Select (1/2/3): " read node_choice case "$node_choice" in 2) log "Installing Latest Node.js..." nvm install node nvm alias default node nvm use node log "Disabling npm logs..." npm config set logs-max 0 ;; 3) log "Skipping Node.js installation." ;; *) log "Installing Latest LTS Node.js..." nvm install --lts nvm alias default 'lts/*' nvm use --lts log "Disabling npm logs..." npm config set logs-max 0 ;; esac } install_python() { log "Fetching latest stable Python version..." LATEST_PY=$(curl -s https://www.python.org/downloads/ | grep -o 'Download Python 3\.[0-9]\{1,2\}\.[0-9]\{1,2\}' | head -1 | grep -o '3\.[0-9]\{1,2\}\.[0-9]\{1,2\}') [[ -z "$LATEST_PY" ]] && LATEST_PY="3.12.3" log "Detected Stable Version: $LATEST_PY" if [[ "$OS" == "macOS" ]]; then log "Downloading official macOS package..." PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg" curl -fsSL -o /tmp/python.pkg "$PKG_URL" log "Running installer..." sudo installer -pkg /tmp/python.pkg -target / rm /tmp/python.pkg else log "Installing build dependencies (zlib, ssl, etc)..." sudo apt-get update && sudo apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev libsqlite3-dev SRC_URL="https://www.python.org/ftp/python/${LATEST_PY}/Python-${LATEST_PY}.tgz" log "Downloading $SRC_URL..." curl -fSL -o /tmp/Python.tgz "$SRC_URL" || err "Failed to download Python source from $SRC_URL" cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}" log "Configuring build (with ensurepip)..." ./configure --enable-optimizations --with-ensurepip=install log "Compiling Python (this may take a few minutes)..." sudo make altinstall PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2) log "Setting up symlinks..." sudo ln -sf /usr/local/bin/python${PY_MINOR} /usr/local/bin/python3 if [[ -f "/usr/local/bin/pip${PY_MINOR}" ]]; then sudo ln -sf /usr/local/bin/pip${PY_MINOR} /usr/local/bin/pip3 log "Successfully linked pip3!" else err "pip${PY_MINOR} was not generated during the build!" fi cd ~ && sudo rm -rf /tmp/Python* fi log "Python $LATEST_PY installation sequence finished!" } install_go() { log "Fetching latest Go version from official source..." LATEST_GO=$(curl -sL https://go.dev/VERSION?m=text | head -n 1) TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz" log "Downloading $TAR_FILE..." curl -fsSL -o /tmp/go.tar.gz "https://go.dev/dl/$TAR_FILE" log "Installing to /usr/local/go..." sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf /tmp/go.tar.gz rm /tmp/go.tar.gz add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"' } install_sdkman() { if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi # SDKMAN explicitly blocks macOS's default Bash 3.2. # If we are trapped in Bash < 4, force the installer to use Zsh. if [[ "$CURRENT_SHELL" == "bash" && "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then log "Outdated Bash detected. Installing SDKMAN via Zsh..." curl -s "https://get.sdkman.io" | zsh else log "Installing SDKMAN via $CURRENT_SHELL..." curl -s "https://get.sdkman.io" | $CURRENT_SHELL fi } install_dotnet() { log "Installing .NET from official script..." curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"' } install_android() { log "Installing Android SDK command-line tools..." if ! require_cmd unzip; then err "unzip is required. Please install 'Build Tools' (Option 1) first." return 1 fi if [[ "$OS_LOWER" == "linux" && "$SYS_ARCH" != "amd64" ]]; then err "Google's Android command-line tools installer supports Linux x86_64 only." return 1 fi ANDROID_HOME="$HOME/Android/Sdk" ANDROID_SDK_ROOT="$ANDROID_HOME" export ANDROID_HOME ANDROID_SDK_ROOT mkdir -p "$ANDROID_HOME/cmdline-tools" if [[ "$OS" == "macOS" ]]; then TOOLS_OS="mac" elif [[ "$OS_LOWER" == "linux" ]]; then TOOLS_OS="linux" else err "Android SDK installation is not supported for $OS." return 1 fi log "Finding latest Android command-line tools from Google..." TOOLS_URL=$(curl -fsSL https://developer.android.com/studio \ | grep -o "https://dl.google.com/android/repository/commandlinetools-${TOOLS_OS}-[0-9]*_latest.zip" \ | head -1) if [[ -z "$TOOLS_URL" ]]; then err "Could not find the Android command-line tools download URL." return 1 fi log "Downloading Android command-line tools..." rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip mkdir -p /tmp/android-cmdline-tools curl -fsSL -o /tmp/android-cmdline-tools.zip "$TOOLS_URL" unzip -q /tmp/android-cmdline-tools.zip -d /tmp/android-cmdline-tools rm -rf "$ANDROID_HOME/cmdline-tools/latest" mkdir -p "$ANDROID_HOME/cmdline-tools/latest" mv /tmp/android-cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/" rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" if [[ ! -x "$SDKMANAGER" ]]; then err "sdkmanager was not installed correctly." return 1 fi log "Accepting Android SDK licenses..." yes | "$SDKMANAGER" --sdk_root="$ANDROID_HOME" --licenses >/dev/null || true log "Installing Android SDK packages..." "$SDKMANAGER" --sdk_root="$ANDROID_HOME" \ "cmdline-tools;latest" \ "platform-tools" \ "emulator" \ "platforms;android-36" \ "build-tools;36.0.0" add_to_path_config "ANDROID_SDK" 'export ANDROID_HOME="$HOME/Android/Sdk" export ANDROID_SDK_ROOT="$ANDROID_HOME" export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"' log "Android SDK installation sequence finished!" } # ======================================================= # CLOUD & INFRASTRUCTURE # ======================================================= install_docker() { log "Checking Docker status..." # 1. Install if missing if ! require_cmd docker; then log "Installing Docker from official source..." if [[ "$OS" == "macOS" ]]; then [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64" DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg" curl -fsSL -o /tmp/Docker.dmg "$DMG_URL" hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker sudo cp -a /Volumes/Docker/Docker.app /Applications/ hdiutil detach /Volumes/Docker rm /tmp/Docker.dmg log "Starting Docker Desktop..." open /Applications/Docker.app log "Please complete the setup in the Docker Desktop UI." else curl -fsSL https://get.docker.com | sudo sh fi else warn "Docker is already installed. Enforcing permissions..." fi # 2. Aggressively enforce Linux permissions if [[ "$OS" != "macOS" ]]; then log "Applying strict Linux post-install actions..." # Ensure docker group exists and user is added sudo groupadd -f docker sudo usermod -aG docker "$USER" # Ensure services are enabled and running if command -v systemctl >/dev/null 2>&1; then sudo systemctl enable --now docker.service sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true elif [[ "$OS" == "WSL" ]]; then sudo service docker start fi # Give the system a second to generate the socket file sleep 2 # Lock in ownership and permissions on the socket if [[ -S /var/run/docker.sock ]]; then log "Securing /var/run/docker.sock..." sudo chown root:docker /var/run/docker.sock sudo chmod 660 /var/run/docker.sock else warn "Docker socket not found. The service may have failed to start." fi log "Added $USER to the docker group and secured the socket." fi } install_aws() { if require_cmd aws; then warn "AWS CLI exists"; return; fi log "Installing AWS CLI directly from Amazon..." if [[ "$OS" == "macOS" ]]; then curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg" sudo installer -pkg /tmp/AWSCLIV2.pkg -target / rm /tmp/AWSCLIV2.pkg else if ! require_cmd unzip; then err "unzip is required. Please install 'Build Tools' (Option 1) first." return 1 fi curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install rm -rf /tmp/awscliv2.zip /tmp/aws fi } install_gcloud() { if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi log "Installing Google Cloud CLI..." curl -fsSL https://sdk.cloud.google.com | $CURRENT_SHELL -s -- --disable-prompts if [[ "$CURRENT_SHELL" == "zsh" ]]; then add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc"' else add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && source "$HOME/google-cloud-sdk/path.bash.inc"' fi } install_firebase() { if require_cmd firebase; then warn "Firebase CLI exists"; return; fi log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..." if ! require_cmd npm; then err "NPM not found. Please install NVM (Option 3) first." return 1 fi npm install -g firebase-tools } # ======================================================= # DEV TOOLS & SECURITY # ======================================================= install_gh() { if require_cmd gh; then warn "GitHub CLI exists"; return; fi log "Fetching latest GitHub CLI version..." LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}" curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz" tar -xzf /tmp/gh.tar.gz -C /tmp sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/ sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz } install_infisical() { if require_cmd infisical; then warn "Infisical CLI exists"; return; fi log "Installing Infisical CLI via NPM..." if ! require_cmd npm; then err "NPM not found. Please install NVM (Option 3) first." return 1 fi npm install -g @infisical/cli } # ======================================================= # AI & AGENTIC TOOLS # ======================================================= install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; } install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; } install_codex() { log "Installing @openai/codex..." if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi npm i -g @openai/codex } # ======================================================= # MENU LOGIC # ======================================================= show_menu() { clear printf "${BLUE}==================================================${NC}\n" printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n" printf "${BLUE}==================================================${NC}\n" printf "${YELLOW}--- Core Runtimes & Managers ---${NC}\n" printf " 1) Build Tools (GCC/Make)\n" printf " 2) Homebrew (Optional)\n" printf " 3) NVM (Node.js)\n" printf " 4) Python 3 (Official API)\n" printf " 5) Go (Official -> /usr/local)\n" printf " 6) SDKMAN (Java/Kotlin)\n" printf " 7) .NET (Official Script)\n" printf " 8) Android SDK (CLI + Platform Tools)\n" printf "${YELLOW}--- Cloud & Infrastructure ---${NC}\n" printf " 9) Docker (Official DMG/sh)\n" printf " 10) AWS CLI (Official Bin)\n" printf " 11) Google Cloud CLI (gcloud)\n" printf " 12) Firebase CLI (NPM/ARM64 Safe)\n" printf "${YELLOW}--- Dev Tools & Security ---${NC}\n" printf " 13) GitHub CLI (Official Bin)\n" printf " 14) Infisical (Official Bin)\n" printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n" printf " 15) Claude Code CLI\n" printf " 16) OpenCode (opencode.ai)\n" printf " 17) OpenAI Codex CLI\n" printf "${BLUE}==================================================${NC}\n" printf " 99) Quit & Refresh Shell\n" printf "${BLUE}==================================================${NC}\n" } while true; do show_menu # Bulletproof prompt for both Bash and Zsh printf "Select options (space-separated): " read input # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells for choice in $input; do case "$choice" in 1) install_build_tools ;; 2) install_brew ;; 3) install_nvm ;; 4) install_python ;; 5) install_go ;; 6) install_sdkman ;; 7) install_dotnet ;; 8) install_android ;; 9) install_docker ;; 10) install_aws ;; 11) install_gcloud ;; 12) install_firebase ;; 13) install_gh ;; 14) install_infisical ;; 15) install_claude ;; 16) install_opencode ;; 17) install_codex ;; 99) log "Installation complete! Refreshing terminal environment..." # If on Linux, forcefully inherit the new docker group without needing a logout if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then exec sg docker -c "exec ${SHELL:-zsh}" else exec "${SHELL:-zsh}" fi ;; *) warn "Option $choice not valid." ;; esac done printf "Press Enter to continue..." read dummy done