Last active 3 weeks ago

Interactive developer environment bootstrapper using official upstream downloads and signed OS-vendor packages.

Revision 8279971e4bf623d77ab3ddd7bb8c2010f3f6f2fe

README.md Raw

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://tinyurl.com/4rskc9e7)"

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 .pkg for 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 official get.docker.com script (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 docker group. 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 unzip to extract the payload.

Built for developers who want total control over their toolchain.

menu.sh Raw
1#!/usr/bin/env bash
2
3# =======================================================
4# 1. BOOTSTRAPPER: PREFER ZSH, FALLBACK TO BASH
5# =======================================================
6if [ -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
22fi
23
24set -u
25
26# =======================================================
27# 2. CROSS-SHELL NORMALIZATION
28# =======================================================
29if [ -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"
34elif [ -n "${BASH_VERSION:-}" ]; then
35 # Bash
36 CURRENT_SHELL="bash"
37 PROFILE_FILE="$HOME/.bashrc"
38else
39 # Fallback POSIX
40 CURRENT_SHELL="sh"
41 PROFILE_FILE="$HOME/.profile"
42fi
43
44# =======================================================
45# COLORS & LOGGING
46# =======================================================
47GREEN="\033[0;32m"
48RED="\033[0;31m"
49YELLOW="\033[1;33m"
50BLUE="\033[0;34m"
51NC="\033[0m"
52
53log() { printf "${GREEN}▶ %s${NC}\n" "$*"; }
54warn() { printf "${YELLOW}⚠ %s${NC}\n" "$*"; }
55err() { printf "${RED}✖ %s${NC}\n" "$*"; }
56info() { printf "${BLUE}ℹ %s${NC}\n" "$*"; }
57
58# =======================================================
59# HELPERS & SYSTEM DETECTION
60# =======================================================
61require_cmd() { command -v "$1" >/dev/null 2>&1; }
62
63add_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
83detect_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
108detect_os_arch
109
110# =======================================================
111# CORE RUNTIMES & MANAGERS
112# =======================================================
113
114install_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
123install_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
139install_nvm() {
140 # 1. Temporarily disable 'unbound variable' strictness for NVM
141 set +u
142
143 export NVM_DIR="$HOME/.nvm"
144
145 if [ -d "$NVM_DIR" ]; then
146 warn "NVM is already installed."
147 else
148 log "Installing NVM via $CURRENT_SHELL..."
149 curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | $CURRENT_SHELL
150 fi
151
152 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
153
154 printf "\n${YELLOW}Which version of Node.js would you like to install?${NC}\n"
155 printf " 1) LTS (Long Term Support - Recommended for stability)\n"
156 printf " 2) Latest (Current features - Recommended for testing new APIs)\n"
157 printf " 3) Skip installing Node.js right now\n"
158
159 printf "Select (1/2/3): "
160 read node_choice
161
162 case "$node_choice" in
163 2)
164 log "Installing Latest Node.js..."
165 nvm install node
166 nvm alias default node
167 nvm use node
168
169 log "Disabling npm logs..."
170 npm config set logs-max 0
171 ;;
172 3)
173 log "Skipping Node.js installation."
174 ;;
175 *)
176 log "Installing Latest LTS Node.js..."
177 nvm install --lts
178 nvm alias default 'lts/*'
179 nvm use --lts
180
181 log "Disabling npm logs..."
182 npm config set logs-max 0
183 ;;
184 esac
185
186 # 2. Re-enable 'unbound variable' strictness for the rest of your script
187 set -u
188}
189
190install_python() {
191 log "Fetching latest stable Python version..."
192
193 LATEST_PY=$(
194 curl -fsSL --compressed https://www.python.org/downloads/ |
195 sed -nE 's/.*Download Python ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' |
196 head -1
197 )
198
199 if [[ -z "$LATEST_PY" ]]; then
200 err "Could not detect the latest stable Python version from python.org."
201 return 1
202 fi
203
204 log "Detected Stable Version: $LATEST_PY"
205
206 if [[ "$OS" == "macOS" ]]; then
207 log "Downloading official macOS package..."
208 PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg"
209 curl -fsSL --compressed -o /tmp/python.pkg "$PKG_URL" || {
210 err "Failed to download Python macOS package from $PKG_URL"
211 return 1
212 }
213
214 log "Running installer..."
215 sudo installer -pkg /tmp/python.pkg -target / || {
216 err "Python macOS installer failed."
217 rm -f /tmp/python.pkg
218 return 1
219 }
220 rm /tmp/python.pkg
221 else
222 log "Installing build dependencies (zlib, ssl, etc)..."
223 sudo apt-get update && sudo apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev libsqlite3-dev || {
224 err "Failed to install Python build dependencies."
225 return 1
226 }
227
228 SRC_URL="https://www.python.org/ftp/python/${LATEST_PY}/Python-${LATEST_PY}.tgz"
229
230 log "Downloading $SRC_URL..."
231 curl -fSL --compressed -o /tmp/Python.tgz "$SRC_URL" || {
232 err "Failed to download Python source from $SRC_URL"
233 return 1
234 }
235
236 cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}" || {
237 err "Failed to extract Python source archive."
238 return 1
239 }
240
241 log "Configuring build (with ensurepip)..."
242 ./configure --enable-optimizations --with-ensurepip=install || {
243 err "Python configure step failed."
244 return 1
245 }
246
247 log "Compiling Python (this may take a few minutes)..."
248 sudo make altinstall || {
249 err "Python build/install step failed."
250 return 1
251 }
252
253 PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2)
254
255 log "Setting up symlinks..."
256 sudo ln -sf /usr/local/bin/python${PY_MINOR} /usr/local/bin/python3
257
258 if [[ -f "/usr/local/bin/pip${PY_MINOR}" ]]; then
259 sudo ln -sf /usr/local/bin/pip${PY_MINOR} /usr/local/bin/pip3
260 log "Successfully linked pip3!"
261 else
262 err "pip${PY_MINOR} was not generated during the build!"
263 fi
264
265 cd ~ && sudo rm -rf /tmp/Python*
266 fi
267
268 log "Python $LATEST_PY installation sequence finished!"
269}
270
271install_go() {
272 log "Fetching latest Go version from official source..."
273 LATEST_GO=$(curl -sL https://go.dev/VERSION?m=text | head -n 1)
274 TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz"
275
276 log "Downloading $TAR_FILE..."
277 curl -fsSL -o /tmp/go.tar.gz "https://go.dev/dl/$TAR_FILE"
278
279 log "Installing to /usr/local/go..."
280 sudo rm -rf /usr/local/go
281 sudo tar -C /usr/local -xzf /tmp/go.tar.gz
282 rm /tmp/go.tar.gz
283 add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"'
284}
285
286install_sdkman() {
287 if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi
288
289 # SDKMAN explicitly blocks macOS's default Bash 3.2.
290 # If we are trapped in Bash < 4, force the installer to use Zsh.
291 if [[ "$CURRENT_SHELL" == "bash" && "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then
292 log "Outdated Bash detected. Installing SDKMAN via Zsh..."
293 curl -s "https://get.sdkman.io" | zsh
294 else
295 log "Installing SDKMAN via $CURRENT_SHELL..."
296 curl -s "https://get.sdkman.io" | $CURRENT_SHELL
297 fi
298}
299
300install_dotnet() {
301 log "Installing .NET from official script..."
302 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
303 add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"'
304}
305
306install_android() {
307 log "Installing Android SDK command-line tools..."
308
309 if ! require_cmd unzip; then
310 err "unzip is required. Please install 'Build Tools' (Option 1) first."
311 return 1
312 fi
313
314 if [[ "$OS_LOWER" == "linux" && "$SYS_ARCH" != "amd64" ]]; then
315 err "Google's Android command-line tools installer supports Linux x86_64 only."
316 return 1
317 fi
318
319 ANDROID_HOME="$HOME/Android/Sdk"
320 ANDROID_SDK_ROOT="$ANDROID_HOME"
321 export ANDROID_HOME ANDROID_SDK_ROOT
322
323 mkdir -p "$ANDROID_HOME/cmdline-tools"
324
325 if [[ "$OS" == "macOS" ]]; then
326 TOOLS_OS="mac"
327 elif [[ "$OS_LOWER" == "linux" ]]; then
328 TOOLS_OS="linux"
329 else
330 err "Android SDK installation is not supported for $OS."
331 return 1
332 fi
333
334 log "Finding latest Android command-line tools from Google..."
335 TOOLS_URL=$(curl -fsSL https://developer.android.com/studio \
336 | grep -o "https://dl.google.com/android/repository/commandlinetools-${TOOLS_OS}-[0-9]*_latest.zip" \
337 | head -1)
338
339 if [[ -z "$TOOLS_URL" ]]; then
340 err "Could not find the Android command-line tools download URL."
341 return 1
342 fi
343
344 log "Downloading Android command-line tools..."
345 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
346 mkdir -p /tmp/android-cmdline-tools
347 curl -fsSL -o /tmp/android-cmdline-tools.zip "$TOOLS_URL"
348 unzip -q /tmp/android-cmdline-tools.zip -d /tmp/android-cmdline-tools
349
350 rm -rf "$ANDROID_HOME/cmdline-tools/latest"
351 mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
352 mv /tmp/android-cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
353 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
354
355 SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
356 if [[ ! -x "$SDKMANAGER" ]]; then
357 err "sdkmanager was not installed correctly."
358 return 1
359 fi
360
361 log "Accepting Android SDK licenses..."
362 yes | "$SDKMANAGER" --sdk_root="$ANDROID_HOME" --licenses >/dev/null || true
363
364 log "Installing Android SDK packages..."
365 "$SDKMANAGER" --sdk_root="$ANDROID_HOME" \
366 "cmdline-tools;latest" \
367 "platform-tools" \
368 "emulator" \
369 "platforms;android-36" \
370 "build-tools;36.0.0"
371
372 add_to_path_config "ANDROID_SDK" 'export ANDROID_HOME="$HOME/Android/Sdk"
373export ANDROID_SDK_ROOT="$ANDROID_HOME"
374export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"'
375
376 log "Android SDK installation sequence finished!"
377}
378
379# =======================================================
380# CLOUD & INFRASTRUCTURE
381# =======================================================
382
383install_docker() {
384 log "Checking Docker status..."
385
386 # 1. Install if missing
387 if ! require_cmd docker; then
388 log "Installing Docker from official source..."
389 if [[ "$OS" == "macOS" ]]; then
390 [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64"
391 DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg"
392 curl -fsSL -o /tmp/Docker.dmg "$DMG_URL"
393 hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker
394 sudo cp -a /Volumes/Docker/Docker.app /Applications/
395 hdiutil detach /Volumes/Docker
396 rm /tmp/Docker.dmg
397
398 log "Starting Docker Desktop..."
399 open /Applications/Docker.app
400 log "Please complete the setup in the Docker Desktop UI."
401 else
402 curl -fsSL https://get.docker.com | sudo sh
403 fi
404 else
405 warn "Docker is already installed. Enforcing permissions..."
406 fi
407
408 # 2. Aggressively enforce Linux permissions
409 if [[ "$OS" != "macOS" ]]; then
410 log "Applying strict Linux post-install actions..."
411
412 # Ensure docker group exists and user is added
413 sudo groupadd -f docker
414 sudo usermod -aG docker "$USER"
415
416 # Ensure services are enabled and running
417 if command -v systemctl >/dev/null 2>&1; then
418 sudo systemctl enable --now docker.service
419 sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true
420 elif [[ "$OS" == "WSL" ]]; then
421 sudo service docker start
422 fi
423
424 # Give the system a second to generate the socket file
425 sleep 2
426
427 # Lock in ownership and permissions on the socket
428 if [[ -S /var/run/docker.sock ]]; then
429 log "Securing /var/run/docker.sock..."
430 sudo chown root:docker /var/run/docker.sock
431 sudo chmod 660 /var/run/docker.sock
432 else
433 warn "Docker socket not found. The service may have failed to start."
434 fi
435
436 log "Added $USER to the docker group and secured the socket."
437 fi
438}
439
440install_aws() {
441 if require_cmd aws; then warn "AWS CLI exists"; return; fi
442 log "Installing AWS CLI directly from Amazon..."
443
444 if [[ "$OS" == "macOS" ]]; then
445 curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg"
446 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
447 rm /tmp/AWSCLIV2.pkg
448 else
449 if ! require_cmd unzip; then
450 err "unzip is required. Please install 'Build Tools' (Option 1) first."
451 return 1
452 fi
453 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip"
454 cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install
455 rm -rf /tmp/awscliv2.zip /tmp/aws
456 fi
457}
458
459install_gcloud() {
460 if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi
461 log "Installing Google Cloud CLI..."
462 curl -fsSL https://sdk.cloud.google.com | $CURRENT_SHELL -s -- --disable-prompts
463
464 if [[ "$CURRENT_SHELL" == "zsh" ]]; then
465 add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc"'
466 else
467 add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && source "$HOME/google-cloud-sdk/path.bash.inc"'
468 fi
469}
470
471install_firebase() {
472 if require_cmd firebase; then warn "Firebase CLI exists"; return; fi
473 log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..."
474
475 if ! require_cmd npm; then
476 err "NPM not found. Please install NVM (Option 3) first."
477 return 1
478 fi
479
480 npm install -g firebase-tools
481}
482
483# =======================================================
484# DEV TOOLS & SECURITY
485# =======================================================
486
487install_gh() {
488 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
489 log "Fetching latest GitHub CLI version..."
490 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
491 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
492
493 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz"
494 tar -xzf /tmp/gh.tar.gz -C /tmp
495 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
496 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
497}
498
499install_infisical() {
500 if require_cmd infisical; then warn "Infisical CLI exists"; return; fi
501 log "Installing Infisical CLI via NPM..."
502
503 if ! require_cmd npm; then
504 err "NPM not found. Please install NVM (Option 3) first."
505 return 1
506 fi
507
508 npm install -g @infisical/cli
509}
510
511# =======================================================
512# AI & AGENTIC TOOLS
513# =======================================================
514
515install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
516install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
517
518install_codex() {
519 log "Installing @openai/codex..."
520 if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi
521 npm i -g @openai/codex
522}
523
524# =======================================================
525# MENU LOGIC
526# =======================================================
527show_menu() {
528 clear
529 printf "${BLUE}==================================================${NC}\n"
530 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
531 printf "${BLUE}==================================================${NC}\n"
532 printf "${YELLOW}--- Core Runtimes & Managers ---${NC}\n"
533 printf " 1) Build Tools (GCC/Make)\n"
534 printf " 2) Homebrew (Optional)\n"
535 printf " 3) NVM (Node.js)\n"
536 printf " 4) Python 3 (Official API)\n"
537 printf " 5) Go (Official -> /usr/local)\n"
538 printf " 6) SDKMAN (Java/Kotlin)\n"
539 printf " 7) .NET (Official Script)\n"
540 printf " 8) Android SDK (CLI + Platform Tools)\n"
541 printf "${YELLOW}--- Cloud & Infrastructure ---${NC}\n"
542 printf " 9) Docker (Official DMG/sh)\n"
543 printf " 10) AWS CLI (Official Bin)\n"
544 printf " 11) Google Cloud CLI (gcloud)\n"
545 printf " 12) Firebase CLI (NPM/ARM64 Safe)\n"
546 printf "${YELLOW}--- Dev Tools & Security ---${NC}\n"
547 printf " 13) GitHub CLI (Official Bin)\n"
548 printf " 14) Infisical (Official Bin)\n"
549 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
550 printf " 15) Claude Code CLI\n"
551 printf " 16) OpenCode (opencode.ai)\n"
552 printf " 17) OpenAI Codex CLI\n"
553 printf "${BLUE}==================================================${NC}\n"
554 printf " 99) Quit & Refresh Shell\n"
555 printf "${BLUE}==================================================${NC}\n"
556}
557
558while true; do
559 show_menu
560
561 # Bulletproof prompt for both Bash and Zsh
562 printf "Select options (space-separated): "
563 read input
564
565 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
566 for choice in $input; do
567 case "$choice" in
568 1) install_build_tools ;;
569 2) install_brew ;;
570 3) install_nvm ;;
571 4) install_python ;;
572 5) install_go ;;
573 6) install_sdkman ;;
574 7) install_dotnet ;;
575 8) install_android ;;
576 9) install_docker ;;
577 10) install_aws ;;
578 11) install_gcloud ;;
579 12) install_firebase ;;
580 13) install_gh ;;
581 14) install_infisical ;;
582 15) install_claude ;;
583 16) install_opencode ;;
584 17) install_codex ;;
585 99)
586 log "Installation complete! Refreshing terminal environment..."
587
588 # If on Linux, forcefully inherit the new docker group without needing a logout
589 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
590 exec sg docker -c "exec ${SHELL:-zsh}"
591 else
592 exec "${SHELL:-zsh}"
593 fi
594 ;;
595 *) warn "Option $choice not valid." ;;
596 esac
597 done
598
599 printf "Press Enter to continue..."
600 read dummy
601done
602