Strict Developer Environment Installer
A robust, idempotent, and interactive bash script to bootstrap your macOS, Linux, or WSL development environment.
Unlike standard setup scripts that rely heavily on package managers like Homebrew or Apt (which can introduce bloat, dependency hell, or outdated packages), this script is designed with a "Strict Official Source" philosophy. It dynamically fetches the absolute latest binaries and pre-compiled releases directly from the official maintainers (e.g., GitHub API, Python.org, Amazon).
✨ Key Features
- Zero-Brew Dependency: By default, it bypasses Homebrew entirely, fetching tools directly from official APIs and release pages (Homebrew is available as an optional install).
- Idempotent: Safe to run multiple times. It intelligently checks if a tool is already installed before attempting to download it.
- Dynamic Versioning: Pings official APIs (like GitHub Releases or
go.dev/VERSION) to ensure you are downloading the exact latest version at the time of execution. - Auto-Path Management: Intelligently injects paths into a dedicated
~/.pathrc(or your.bashrc/.zshrc) without mangling your config files. - Architecture Aware: Automatically detects if you are running on Intel (
x86_64) or Apple Silicon/ARM (aarch64/arm64) and downloads the correct binaries.
🚀 Usage
You can launch the interactive installer directly from your terminal with a single command:
bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/af1c64c143a44ffbb0a1632dd6a32af1/raw/HEAD/menu.sh)"
Once the menu loads, simply follow the interactive prompts. You can select multiple tools at once by entering space-separated numbers (e.g., 1 3 8 12).
🛠️ Included Tools
Core Runtimes & Managers
- Build Tools: GCC, Make, Git, Curl, Unzip, and core libraries.
- Homebrew: (Optional) Installed officially.
- NVM (Node Version Manager): Includes interactive prompt to install LTS or Latest Node.js immediately.
- Python 3: Fetches the latest stable
.pkgfor Mac or compiles strictly from source for Linux. - Go: Fetches the latest tarball and installs system-wide to
/usr/local/go. - SDKMAN: For managing Java, Kotlin, and Gradle.
- .NET: Installed via Microsoft's official script.
Cloud & Infrastructure
- Docker: Downloads the official
.dmg(macOS) or runs the officialget.docker.comscript (Linux) with auto-group assignment. - AWS CLI: Fetches the official binaries directly from Amazon.
- Google Cloud CLI (gcloud): Official Google setup script.
- Firebase CLI: Standalone binary (no Node.js required natively).
Dev Tools & Security
- GitHub CLI (gh): Downloaded directly from GitHub Releases to
/usr/local/bin. - Infisical CLI: Downloaded directly from GitHub Releases for secret management.
AI & Agentic Tools
- Claude Code CLI: Anthropic's official agent.
- OpenCode: AI agent orchestration.
- OpenAI Codex CLI: Required NVM/Node.js to run.
⚠️ Important Notes & Troubleshooting
- Restart Your Shell: After running the script for the first time, you should restart your terminal or run
source ~/.bashrc(or~/.zshrc) to ensure all new PATH variables (like NVM, Go, or .NET) take effect. - Docker Permissions (Linux): The script automatically adds your user to the
dockergroup. You will need to log out and log back in for this to take effect. - AWS CLI on Linux: Ensure you install Build Tools (Option 1) first if you are on a fresh Linux install, as the AWS installer requires
unzipto extract the payload.
Built for developers who want total control over their toolchain.
| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | # ======================================================= |
| 4 | # 1. BOOTSTRAPPER: PREFER ZSH, FALLBACK TO BASH |
| 5 | # ======================================================= |
| 6 | if [ -z "${_PREFER_ZSH_BOOTSTRAPPED:-}" ]; then |
| 7 | export _PREFER_ZSH_BOOTSTRAPPED=1 |
| 8 | |
| 9 | # Only attempt to re-execute if $0 is an actual file on disk. |
| 10 | # This prevents the 'can't open input file' error when running |
| 11 | # from memory via `bash -c "$(curl ...)"` or `zsh -c`. |
| 12 | if [ -f "$0" ]; then |
| 13 | if command -v zsh >/dev/null 2>&1; then |
| 14 | exec zsh "$0" "$@" |
| 15 | elif command -v bash >/dev/null 2>&1; then |
| 16 | exec bash "$0" "$@" |
| 17 | else |
| 18 | echo "Error: Neither Zsh nor Bash is installed. Exiting." |
| 19 | exit 1 |
| 20 | fi |
| 21 | fi |
| 22 | fi |
| 23 | |
| 24 | set -u |
| 25 | |
| 26 | # ======================================================= |
| 27 | # 2. CROSS-SHELL NORMALIZATION |
| 28 | # ======================================================= |
| 29 | if [ -n "${ZSH_VERSION:-}" ]; then |
| 30 | # Zsh: Enable Bash-like word splitting for unquoted variables (menu input) |
| 31 | setopt shwordsplit 2>/dev/null || true |
| 32 | CURRENT_SHELL="zsh" |
| 33 | PROFILE_FILE="$HOME/.zshrc" |
| 34 | elif [ -n "${BASH_VERSION:-}" ]; then |
| 35 | # Bash |
| 36 | CURRENT_SHELL="bash" |
| 37 | PROFILE_FILE="$HOME/.bashrc" |
| 38 | else |
| 39 | # Fallback POSIX |
| 40 | CURRENT_SHELL="sh" |
| 41 | PROFILE_FILE="$HOME/.profile" |
| 42 | fi |
| 43 | |
| 44 | # ======================================================= |
| 45 | # COLORS & LOGGING |
| 46 | # ======================================================= |
| 47 | GREEN="\033[0;32m" |
| 48 | RED="\033[0;31m" |
| 49 | YELLOW="\033[1;33m" |
| 50 | BLUE="\033[0;34m" |
| 51 | NC="\033[0m" |
| 52 | |
| 53 | log() { printf "${GREEN}▶ %s${NC}\n" "$*"; } |
| 54 | warn() { printf "${YELLOW}⚠ %s${NC}\n" "$*"; } |
| 55 | err() { printf "${RED}✖ %s${NC}\n" "$*"; } |
| 56 | info() { printf "${BLUE}ℹ %s${NC}\n" "$*"; } |
| 57 | |
| 58 | # ======================================================= |
| 59 | # HELPERS & SYSTEM DETECTION |
| 60 | # ======================================================= |
| 61 | require_cmd() { command -v "$1" >/dev/null 2>&1; } |
| 62 | |
| 63 | add_to_path_config() { |
| 64 | local label=$1 |
| 65 | local path_line=$2 |
| 66 | local target_file="${3:-$PROFILE_FILE}" |
| 67 | |
| 68 | if [[ -f "$target_file" ]]; then |
| 69 | if ! grep -q "$label" "$target_file"; then |
| 70 | printf "\n# %s\n%s\n" "$label" "$path_line" >> "$target_file" |
| 71 | log "Added $label to $target_file" |
| 72 | fi |
| 73 | else |
| 74 | printf "\n# %s\n%s\n" "$label" "$path_line" >> "$PROFILE_FILE" |
| 75 | log "Added $label to shell profile ($PROFILE_FILE)." |
| 76 | fi |
| 77 | |
| 78 | # Instantly evaluate the path line so the current script session |
| 79 | # can immediately use the newly installed tool in subsequent steps. |
| 80 | eval "$path_line" 2>/dev/null || true |
| 81 | } |
| 82 | |
| 83 | detect_os_arch() { |
| 84 | ARCH=$(uname -m) |
| 85 | case "$ARCH" in |
| 86 | x86_64|amd64) SYS_ARCH="amd64"; MAC_ARCH="x86_64"; AWS_ARCH="x86_64" ;; |
| 87 | aarch64|arm64) SYS_ARCH="arm64"; MAC_ARCH="arm64"; AWS_ARCH="aarch64" ;; |
| 88 | *) err "Unsupported architecture: $ARCH"; exit 1 ;; |
| 89 | esac |
| 90 | |
| 91 | if [[ "$OSTYPE" == "darwin"* ]]; then |
| 92 | OS="macOS" |
| 93 | OS_LOWER="darwin" |
| 94 | elif grep -qi microsoft /proc/version 2>/dev/null; then |
| 95 | OS="WSL" |
| 96 | OS_LOWER="linux" |
| 97 | elif [[ -f /etc/os-release ]]; then |
| 98 | . /etc/os-release |
| 99 | [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* ]] && OS="Ubuntu" || OS="Linux" |
| 100 | OS_LOWER="linux" |
| 101 | else |
| 102 | OS="Unknown" |
| 103 | OS_LOWER="unknown" |
| 104 | fi |
| 105 | log "Detected: $OS ($ARCH) running $CURRENT_SHELL" |
| 106 | } |
| 107 | |
| 108 | detect_os_arch |
| 109 | |
| 110 | # ======================================================= |
| 111 | # CORE RUNTIMES & MANAGERS |
| 112 | # ======================================================= |
| 113 | |
| 114 | install_build_tools() { |
| 115 | log "Installing Build Essentials & Core Dependencies..." |
| 116 | case "$OS" in |
| 117 | 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 ;; |
| 118 | macOS) xcode-select --install || warn "Xcode tools already installed" ;; |
| 119 | *) warn "Manual installation required for $OS." ;; |
| 120 | esac |
| 121 | } |
| 122 | |
| 123 | install_brew() { |
| 124 | if require_cmd brew; then warn "Homebrew already installed"; return; fi |
| 125 | log "Installing Homebrew from Official Source..." |
| 126 | |
| 127 | # Homebrew's installer specifically requires Bash execution, regardless of current shell |
| 128 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| 129 | |
| 130 | if [[ "$OS_LOWER" == "linux" ]]; then |
| 131 | add_to_path_config "HOMEBREW" 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' |
| 132 | elif [[ "$MAC_ARCH" == "arm64" ]]; then |
| 133 | add_to_path_config "HOMEBREW" 'eval "$(/opt/homebrew/bin/brew shellenv)"' |
| 134 | else |
| 135 | add_to_path_config "HOMEBREW" 'eval "$(/usr/local/bin/brew shellenv)"' |
| 136 | fi |
| 137 | } |
| 138 | |
| 139 | install_nvm() { |
| 140 | export NVM_DIR="$HOME/.nvm" |
| 141 | |
| 142 | if [ -d "$NVM_DIR" ]; then |
| 143 | warn "NVM is already installed." |
| 144 | else |
| 145 | log "Installing NVM via $CURRENT_SHELL..." |
| 146 | curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | $CURRENT_SHELL |
| 147 | fi |
| 148 | |
| 149 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" |
| 150 | |
| 151 | printf "\n${YELLOW}Which version of Node.js would you like to install?${NC}\n" |
| 152 | printf " 1) LTS (Long Term Support - Recommended for stability)\n" |
| 153 | printf " 2) Latest (Current features - Recommended for testing new APIs)\n" |
| 154 | printf " 3) Skip installing Node.js right now\n" |
| 155 | |
| 156 | printf "Select (1/2/3): " |
| 157 | read node_choice |
| 158 | |
| 159 | case "$node_choice" in |
| 160 | 2) |
| 161 | log "Installing Latest Node.js..." |
| 162 | nvm install node |
| 163 | nvm alias default node |
| 164 | nvm use node |
| 165 | |
| 166 | log "Disabling npm logs..." |
| 167 | npm config set logs-max 0 |
| 168 | ;; |
| 169 | 3) |
| 170 | log "Skipping Node.js installation." |
| 171 | ;; |
| 172 | *) |
| 173 | log "Installing Latest LTS Node.js..." |
| 174 | nvm install --lts |
| 175 | nvm alias default 'lts/*' |
| 176 | nvm use --lts |
| 177 | |
| 178 | log "Disabling npm logs..." |
| 179 | npm config set logs-max 0 |
| 180 | ;; |
| 181 | esac |
| 182 | } |
| 183 | |
| 184 | install_python() { |
| 185 | log "Fetching latest stable Python version..." |
| 186 | |
| 187 | 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\}') |
| 188 | [[ -z "$LATEST_PY" ]] && LATEST_PY="3.12.3" |
| 189 | |
| 190 | log "Detected Stable Version: $LATEST_PY" |
| 191 | |
| 192 | if [[ "$OS" == "macOS" ]]; then |
| 193 | log "Downloading official macOS package..." |
| 194 | PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg" |
| 195 | curl -fsSL -o /tmp/python.pkg "$PKG_URL" |
| 196 | |
| 197 | log "Running installer..." |
| 198 | sudo installer -pkg /tmp/python.pkg -target / |
| 199 | rm /tmp/python.pkg |
| 200 | else |
| 201 | log "Installing build dependencies (zlib, ssl, etc)..." |
| 202 | sudo apt-get update && sudo apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev libsqlite3-dev |
| 203 | |
| 204 | SRC_URL="https://www.python.org/ftp/python/${LATEST_PY}/Python-${LATEST_PY}.tgz" |
| 205 | |
| 206 | log "Downloading $SRC_URL..." |
| 207 | curl -fSL -o /tmp/Python.tgz "$SRC_URL" || err "Failed to download Python source from $SRC_URL" |
| 208 | |
| 209 | cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}" |
| 210 | |
| 211 | log "Configuring build (with ensurepip)..." |
| 212 | ./configure --enable-optimizations --with-ensurepip=install |
| 213 | |
| 214 | log "Compiling Python (this may take a few minutes)..." |
| 215 | sudo make altinstall |
| 216 | |
| 217 | PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2) |
| 218 | |
| 219 | log "Setting up symlinks..." |
| 220 | sudo ln -sf /usr/local/bin/python${PY_MINOR} /usr/local/bin/python3 |
| 221 | |
| 222 | if [[ -f "/usr/local/bin/pip${PY_MINOR}" ]]; then |
| 223 | sudo ln -sf /usr/local/bin/pip${PY_MINOR} /usr/local/bin/pip3 |
| 224 | log "Successfully linked pip3!" |
| 225 | else |
| 226 | err "pip${PY_MINOR} was not generated during the build!" |
| 227 | fi |
| 228 | |
| 229 | cd ~ && sudo rm -rf /tmp/Python* |
| 230 | fi |
| 231 | |
| 232 | log "Python $LATEST_PY installation sequence finished!" |
| 233 | } |
| 234 | |
| 235 | install_go() { |
| 236 | log "Fetching latest Go version from official source..." |
| 237 | LATEST_GO=$(curl -sL https://go.dev/VERSION?m=text | head -n 1) |
| 238 | TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz" |
| 239 | |
| 240 | log "Downloading $TAR_FILE..." |
| 241 | curl -fsSL -o /tmp/go.tar.gz "https://go.dev/dl/$TAR_FILE" |
| 242 | |
| 243 | log "Installing to /usr/local/go..." |
| 244 | sudo rm -rf /usr/local/go |
| 245 | sudo tar -C /usr/local -xzf /tmp/go.tar.gz |
| 246 | rm /tmp/go.tar.gz |
| 247 | add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"' |
| 248 | } |
| 249 | |
| 250 | install_sdkman() { |
| 251 | if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi |
| 252 | |
| 253 | # SDKMAN explicitly blocks macOS's default Bash 3.2. |
| 254 | # If we are trapped in Bash < 4, force the installer to use Zsh. |
| 255 | if [[ "$CURRENT_SHELL" == "bash" && "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then |
| 256 | log "Outdated Bash detected. Installing SDKMAN via Zsh..." |
| 257 | curl -s "https://get.sdkman.io" | zsh |
| 258 | else |
| 259 | log "Installing SDKMAN via $CURRENT_SHELL..." |
| 260 | curl -s "https://get.sdkman.io" | $CURRENT_SHELL |
| 261 | fi |
| 262 | } |
| 263 | |
| 264 | install_dotnet() { |
| 265 | log "Installing .NET from official script..." |
| 266 | curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL |
| 267 | add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"' |
| 268 | } |
| 269 | |
| 270 | # ======================================================= |
| 271 | # CLOUD & INFRASTRUCTURE |
| 272 | # ======================================================= |
| 273 | |
| 274 | install_docker() { |
| 275 | log "Checking Docker status..." |
| 276 | |
| 277 | # 1. Install if missing |
| 278 | if ! require_cmd docker; then |
| 279 | log "Installing Docker from official source..." |
| 280 | if [[ "$OS" == "macOS" ]]; then |
| 281 | [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64" |
| 282 | DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg" |
| 283 | curl -fsSL -o /tmp/Docker.dmg "$DMG_URL" |
| 284 | hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker |
| 285 | sudo cp -a /Volumes/Docker/Docker.app /Applications/ |
| 286 | hdiutil detach /Volumes/Docker |
| 287 | rm /tmp/Docker.dmg |
| 288 | |
| 289 | log "Starting Docker Desktop..." |
| 290 | open /Applications/Docker.app |
| 291 | log "Please complete the setup in the Docker Desktop UI." |
| 292 | else |
| 293 | curl -fsSL https://get.docker.com | sudo sh |
| 294 | fi |
| 295 | else |
| 296 | warn "Docker is already installed. Enforcing permissions..." |
| 297 | fi |
| 298 | |
| 299 | # 2. Aggressively enforce Linux permissions |
| 300 | if [[ "$OS" != "macOS" ]]; then |
| 301 | log "Applying strict Linux post-install actions..." |
| 302 | |
| 303 | # Ensure docker group exists and user is added |
| 304 | sudo groupadd -f docker |
| 305 | sudo usermod -aG docker "$USER" |
| 306 | |
| 307 | # Ensure services are enabled and running |
| 308 | if command -v systemctl >/dev/null 2>&1; then |
| 309 | sudo systemctl enable --now docker.service |
| 310 | sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true |
| 311 | elif [[ "$OS" == "WSL" ]]; then |
| 312 | sudo service docker start |
| 313 | fi |
| 314 | |
| 315 | # Give the system a second to generate the socket file |
| 316 | sleep 2 |
| 317 | |
| 318 | # Lock in ownership and permissions on the socket |
| 319 | if [[ -S /var/run/docker.sock ]]; then |
| 320 | log "Securing /var/run/docker.sock..." |
| 321 | sudo chown root:docker /var/run/docker.sock |
| 322 | sudo chmod 660 /var/run/docker.sock |
| 323 | else |
| 324 | warn "Docker socket not found. The service may have failed to start." |
| 325 | fi |
| 326 | |
| 327 | log "Added $USER to the docker group and secured the socket." |
| 328 | fi |
| 329 | } |
| 330 | |
| 331 | install_aws() { |
| 332 | if require_cmd aws; then warn "AWS CLI exists"; return; fi |
| 333 | log "Installing AWS CLI directly from Amazon..." |
| 334 | |
| 335 | if [[ "$OS" == "macOS" ]]; then |
| 336 | curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg" |
| 337 | sudo installer -pkg /tmp/AWSCLIV2.pkg -target / |
| 338 | rm /tmp/AWSCLIV2.pkg |
| 339 | else |
| 340 | if ! require_cmd unzip; then |
| 341 | err "unzip is required. Please install 'Build Tools' (Option 1) first." |
| 342 | return 1 |
| 343 | fi |
| 344 | curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" |
| 345 | cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install |
| 346 | rm -rf /tmp/awscliv2.zip /tmp/aws |
| 347 | fi |
| 348 | } |
| 349 | |
| 350 | install_gcloud() { |
| 351 | if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi |
| 352 | log "Installing Google Cloud CLI..." |
| 353 | curl -fsSL https://sdk.cloud.google.com | $CURRENT_SHELL -s -- --disable-prompts |
| 354 | |
| 355 | if [[ "$CURRENT_SHELL" == "zsh" ]]; then |
| 356 | add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc"' |
| 357 | else |
| 358 | add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && source "$HOME/google-cloud-sdk/path.bash.inc"' |
| 359 | fi |
| 360 | } |
| 361 | |
| 362 | install_firebase() { |
| 363 | if require_cmd firebase; then warn "Firebase CLI exists"; return; fi |
| 364 | log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..." |
| 365 | |
| 366 | if ! require_cmd npm; then |
| 367 | err "NPM not found. Please install NVM (Option 3) first." |
| 368 | return 1 |
| 369 | fi |
| 370 | |
| 371 | npm install -g firebase-tools |
| 372 | } |
| 373 | |
| 374 | # ======================================================= |
| 375 | # DEV TOOLS & SECURITY |
| 376 | # ======================================================= |
| 377 | |
| 378 | install_gh() { |
| 379 | if require_cmd gh; then warn "GitHub CLI exists"; return; fi |
| 380 | log "Fetching latest GitHub CLI version..." |
| 381 | LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') |
| 382 | TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}" |
| 383 | |
| 384 | curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz" |
| 385 | tar -xzf /tmp/gh.tar.gz -C /tmp |
| 386 | sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/ |
| 387 | sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz |
| 388 | } |
| 389 | |
| 390 | install_infisical() { |
| 391 | if require_cmd infisical; then warn "Infisical CLI exists"; return; fi |
| 392 | log "Installing Infisical CLI via NPM..." |
| 393 | |
| 394 | if ! require_cmd npm; then |
| 395 | err "NPM not found. Please install NVM (Option 3) first." |
| 396 | return 1 |
| 397 | fi |
| 398 | |
| 399 | npm install -g @infisical/cli |
| 400 | } |
| 401 | |
| 402 | # ======================================================= |
| 403 | # AI & AGENTIC TOOLS |
| 404 | # ======================================================= |
| 405 | |
| 406 | install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; } |
| 407 | install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; } |
| 408 | |
| 409 | install_codex() { |
| 410 | log "Installing @openai/codex..." |
| 411 | if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi |
| 412 | npm i -g @openai/codex |
| 413 | } |
| 414 | |
| 415 | # ======================================================= |
| 416 | # MENU LOGIC |
| 417 | # ======================================================= |
| 418 | show_menu() { |
| 419 | clear |
| 420 | printf "${BLUE}==================================================${NC}\n" |
| 421 | printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n" |
| 422 | printf "${BLUE}==================================================${NC}\n" |
| 423 | printf "${YELLOW}--- Core Runtimes & Managers ---${NC}\n" |
| 424 | printf " 1) Build Tools (GCC/Make)\n" |
| 425 | printf " 2) Homebrew (Optional)\n" |
| 426 | printf " 3) NVM (Node.js)\n" |
| 427 | printf " 4) Python 3 (Official API)\n" |
| 428 | printf " 5) Go (Official -> /usr/local)\n" |
| 429 | printf " 6) SDKMAN (Java/Kotlin)\n" |
| 430 | printf " 7) .NET (Official Script)\n" |
| 431 | printf "${YELLOW}--- Cloud & Infrastructure ---${NC}\n" |
| 432 | printf " 8) Docker (Official DMG/sh)\n" |
| 433 | printf " 9) AWS CLI (Official Bin)\n" |
| 434 | printf " 10) Google Cloud CLI (gcloud)\n" |
| 435 | printf " 11) Firebase CLI (NPM/ARM64 Safe)\n" |
| 436 | printf "${YELLOW}--- Dev Tools & Security ---${NC}\n" |
| 437 | printf " 12) GitHub CLI (Official Bin)\n" |
| 438 | printf " 13) Infisical (Official Bin)\n" |
| 439 | printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n" |
| 440 | printf " 14) Claude Code CLI\n" |
| 441 | printf " 15) OpenCode (opencode.ai)\n" |
| 442 | printf " 16) OpenAI Codex CLI\n" |
| 443 | printf "${BLUE}==================================================${NC}\n" |
| 444 | printf " 99) Quit & Refresh Shell\n" |
| 445 | printf "${BLUE}==================================================${NC}\n" |
| 446 | } |
| 447 | |
| 448 | while true; do |
| 449 | show_menu |
| 450 | |
| 451 | # Bulletproof prompt for both Bash and Zsh |
| 452 | printf "Select options (space-separated): " |
| 453 | read input |
| 454 | |
| 455 | # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells |
| 456 | for choice in $input; do |
| 457 | case "$choice" in |
| 458 | 1) install_build_tools ;; |
| 459 | 2) install_brew ;; |
| 460 | 3) install_nvm ;; |
| 461 | 4) install_python ;; |
| 462 | 5) install_go ;; |
| 463 | 6) install_sdkman ;; |
| 464 | 7) install_dotnet ;; |
| 465 | 8) install_docker ;; |
| 466 | 9) install_aws ;; |
| 467 | 10) install_gcloud ;; |
| 468 | 11) install_firebase ;; |
| 469 | 12) install_gh ;; |
| 470 | 13) install_infisical ;; |
| 471 | 14) install_claude ;; |
| 472 | 15) install_opencode ;; |
| 473 | 16) install_codex ;; |
| 474 | 99) |
| 475 | log "Installation complete! Refreshing terminal environment..." |
| 476 | |
| 477 | # If on Linux, forcefully inherit the new docker group without needing a logout |
| 478 | if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then |
| 479 | exec sg docker -c "exec ${SHELL:-zsh}" |
| 480 | else |
| 481 | exec "${SHELL:-zsh}" |
| 482 | fi |
| 483 | ;; |
| 484 | *) warn "Option $choice not valid." ;; |
| 485 | esac |
| 486 | done |
| 487 | |
| 488 | printf "Press Enter to continue..." |
| 489 | read dummy |
| 490 | done |