Última actividad 3 weeks ago

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

Revisión df39699d00c1cc239bf3d7a4953219e981d57fdc

README.md Sin formato

Strict Developer Environment Installer

A robust, idempotent, and interactive bash script to bootstrap your macOS, Linux, or WSL development environment.

This script follows a "Strict Official Source" philosophy. It fetches binaries and installers directly from official maintainers, or from the operating system vendor's signed repositories when upstream does not publish native binaries for that platform.

✨ Key Features

  • Zero-Brew Dependency: By default, it bypasses Homebrew entirely, using official APIs, release pages, and signed OS repositories (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: Uses official APIs (like GitHub Releases or go.dev/VERSION) when maintainers publish versioned binaries. OS-packaged tools track the supported distribution version and security updates.
  • 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).

Choose 19) Update All Installed Software to update every detected tool in the menu from its official source. Tools that are not installed are skipped.

🛠️ Included Tools

Essentials & Package Managers

  • Build Tools: GCC, Make, Git, Curl, Unzip, and core libraries.
  • Homebrew: (Optional) Installed officially.

Languages & Runtimes

  • NVM (Node Version Manager): Includes interactive prompt to install LTS or Latest Node.js immediately.
  • Python 3: Fetches Python.org's latest stable .pkg for macOS. Linux and WSL install precompiled Python, pip, and venv from the signed OS-vendor repository without source compilation or third-party repositories.
  • 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.

Mobile Development

  • Android SDK: Installs command-line tools, platform tools, emulator, and current Android platform/build tools.

Containers & Infrastructure

  • Docker: Downloads the official .dmg (macOS) or runs the official get.docker.com script (Linux) with auto-group assignment.
  • Portainer: Deploys Portainer CE as a Docker container with persistent storage.

Cloud & App Platforms

  • AWS CLI: Fetches the official binaries directly from Amazon.
  • Google Cloud CLI (gcloud): Official Google setup script, installed at /opt/google-cloud-sdk.
  • Firebase CLI: Installs firebase-tools via NPM, so Node.js/NPM is required.

Developer Productivity

  • GitHub CLI (gh): Downloaded directly from GitHub Releases to /usr/local/bin.

Secrets & Security

  • Infisical CLI: Installs @infisical/cli via NPM for secret management, so Node.js/NPM is required.

AI & Agentic Tools

  • Claude Code CLI: Anthropic's official agent.
  • OpenAI Codex CLI: Required NVM/Node.js to run.
  • OpenCode: AI agent orchestration installed from the official OpenCode installer.

⚠️ 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.
  • Python Version on Linux: Python follows the version supported by your distribution rather than the newest python.org patch release. This keeps installation precompiled, vendor-supported, and eligible for normal OS security updates.

Built for developers who want total control over their toolchain.

menu.sh Sin formato
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 if [[ "$OS" == "macOS" ]]; then
192 log "Fetching latest stable Python version..."
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 if [[ -z "$LATEST_PY" ]]; then
199 err "Could not detect the latest stable Python version from python.org."
200 return 1
201 fi
202
203 log "Detected Stable Version: $LATEST_PY"
204 log "Downloading official macOS package..."
205 PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg"
206 curl -fsSL --compressed -o /tmp/python.pkg "$PKG_URL" || {
207 err "Failed to download Python macOS package from $PKG_URL"
208 return 1
209 }
210
211 log "Running installer..."
212 sudo installer -pkg /tmp/python.pkg -target / || {
213 err "Python macOS installer failed."
214 rm -f /tmp/python.pkg
215 return 1
216 }
217 rm /tmp/python.pkg
218 else
219 log "Installing precompiled Python from the official OS repository..."
220 if require_cmd apt-get; then
221 sudo apt-get update && \
222 sudo apt-get install -y python3 python3-pip python3-venv
223 elif require_cmd dnf; then
224 sudo dnf install -y python3 python3-pip
225 elif require_cmd zypper; then
226 sudo zypper --non-interactive install python3 python3-pip
227 elif require_cmd pacman; then
228 sudo pacman -Sy --needed python python-pip
229 elif require_cmd apk; then
230 sudo apk add python3 py3-pip py3-virtualenv
231 else
232 err "No supported official OS package manager found for Python."
233 return 1
234 fi
235 fi
236
237 require_cmd python3 || { err "python3 was not installed successfully."; return 1; }
238 python3 -m pip --version >/dev/null 2>&1 || {
239 err "pip is unavailable for the installed Python."
240 return 1
241 }
242 python3 -m venv --help >/dev/null 2>&1 || {
243 err "venv is unavailable for the installed Python."
244 return 1
245 }
246
247 log "$(python3 --version) installed with pip and venv support."
248}
249
250install_go() {
251 log "Fetching latest Go version from official source..."
252 LATEST_GO=$(curl -sL https://go.dev/VERSION?m=text | head -n 1)
253 TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz"
254
255 log "Downloading $TAR_FILE..."
256 curl -fsSL -o /tmp/go.tar.gz "https://go.dev/dl/$TAR_FILE"
257
258 log "Installing to /usr/local/go..."
259 sudo rm -rf /usr/local/go
260 sudo tar -C /usr/local -xzf /tmp/go.tar.gz
261 rm /tmp/go.tar.gz
262 add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"'
263}
264
265install_sdkman() {
266 if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi
267
268 # SDKMAN explicitly blocks macOS's default Bash 3.2.
269 # If we are trapped in Bash < 4, force the installer to use Zsh.
270 if [[ "$CURRENT_SHELL" == "bash" && "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then
271 log "Outdated Bash detected. Installing SDKMAN via Zsh..."
272 curl -s "https://get.sdkman.io" | zsh
273 else
274 log "Installing SDKMAN via $CURRENT_SHELL..."
275 curl -s "https://get.sdkman.io" | $CURRENT_SHELL
276 fi
277}
278
279install_dotnet() {
280 log "Installing .NET from official script..."
281 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
282 add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"'
283}
284
285install_android() {
286 log "Installing Android SDK command-line tools..."
287
288 if ! require_cmd unzip; then
289 err "unzip is required. Please install 'Build Tools' (Option 1) first."
290 return 1
291 fi
292
293 if [[ "$OS_LOWER" == "linux" && "$SYS_ARCH" != "amd64" ]]; then
294 err "Google's Android command-line tools installer supports Linux x86_64 only."
295 return 1
296 fi
297
298 ANDROID_HOME="$HOME/Android/Sdk"
299 ANDROID_SDK_ROOT="$ANDROID_HOME"
300 export ANDROID_HOME ANDROID_SDK_ROOT
301
302 mkdir -p "$ANDROID_HOME/cmdline-tools"
303
304 if [[ "$OS" == "macOS" ]]; then
305 TOOLS_OS="mac"
306 elif [[ "$OS_LOWER" == "linux" ]]; then
307 TOOLS_OS="linux"
308 else
309 err "Android SDK installation is not supported for $OS."
310 return 1
311 fi
312
313 log "Finding latest Android command-line tools from Google..."
314 TOOLS_URL=$(curl -fsSL https://developer.android.com/studio \
315 | grep -o "https://dl.google.com/android/repository/commandlinetools-${TOOLS_OS}-[0-9]*_latest.zip" \
316 | head -1)
317
318 if [[ -z "$TOOLS_URL" ]]; then
319 err "Could not find the Android command-line tools download URL."
320 return 1
321 fi
322
323 log "Downloading Android command-line tools..."
324 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
325 mkdir -p /tmp/android-cmdline-tools
326 curl -fsSL -o /tmp/android-cmdline-tools.zip "$TOOLS_URL"
327 unzip -q /tmp/android-cmdline-tools.zip -d /tmp/android-cmdline-tools
328
329 rm -rf "$ANDROID_HOME/cmdline-tools/latest"
330 mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
331 mv /tmp/android-cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
332 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
333
334 SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
335 if [[ ! -x "$SDKMANAGER" ]]; then
336 err "sdkmanager was not installed correctly."
337 return 1
338 fi
339
340 log "Accepting Android SDK licenses..."
341 yes | "$SDKMANAGER" --sdk_root="$ANDROID_HOME" --licenses >/dev/null || true
342
343 log "Installing Android SDK packages..."
344 "$SDKMANAGER" --sdk_root="$ANDROID_HOME" \
345 "cmdline-tools;latest" \
346 "platform-tools" \
347 "emulator" \
348 "platforms;android-36" \
349 "build-tools;36.0.0"
350
351 add_to_path_config "ANDROID_SDK" 'export ANDROID_HOME="$HOME/Android/Sdk"
352export ANDROID_SDK_ROOT="$ANDROID_HOME"
353export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"'
354
355 log "Android SDK installation sequence finished!"
356}
357
358# =======================================================
359# CLOUD & INFRASTRUCTURE
360# =======================================================
361
362install_docker() {
363 log "Checking Docker status..."
364
365 # 1. Install if missing
366 if ! require_cmd docker; then
367 log "Installing Docker from official source..."
368 if [[ "$OS" == "macOS" ]]; then
369 [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64"
370 DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg"
371 curl -fsSL -o /tmp/Docker.dmg "$DMG_URL"
372 hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker
373 sudo cp -a /Volumes/Docker/Docker.app /Applications/
374 hdiutil detach /Volumes/Docker
375 rm /tmp/Docker.dmg
376
377 log "Starting Docker Desktop..."
378 open /Applications/Docker.app
379 log "Please complete the setup in the Docker Desktop UI."
380 else
381 curl -fsSL https://get.docker.com | sudo sh
382 fi
383 else
384 warn "Docker is already installed. Enforcing permissions..."
385 fi
386
387 # 2. Aggressively enforce Linux permissions
388 if [[ "$OS" != "macOS" ]]; then
389 log "Applying strict Linux post-install actions..."
390
391 # Ensure docker group exists and user is added
392 sudo groupadd -f docker
393 sudo usermod -aG docker "$USER"
394
395 # Ensure services are enabled and running
396 if command -v systemctl >/dev/null 2>&1; then
397 sudo systemctl enable --now docker.service
398 sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true
399 elif [[ "$OS" == "WSL" ]]; then
400 sudo service docker start
401 fi
402
403 # Give the system a second to generate the socket file
404 sleep 2
405
406 # Lock in ownership and permissions on the socket
407 if [[ -S /var/run/docker.sock ]]; then
408 log "Securing /var/run/docker.sock..."
409 sudo chown root:docker /var/run/docker.sock
410 sudo chmod 660 /var/run/docker.sock
411 else
412 warn "Docker socket not found. The service may have failed to start."
413 fi
414
415 log "Added $USER to the docker group and secured the socket."
416 fi
417}
418
419install_portainer() {
420 log "Installing Portainer CE..."
421
422 if ! require_cmd docker; then
423 err "Docker is required. Please install Docker (Option 9) first."
424 return 1
425 fi
426
427 if ! docker info >/dev/null 2>&1; then
428 err "Docker is installed but the daemon is not running. Start Docker, then retry Portainer."
429 return 1
430 fi
431
432 if docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
433 warn "Portainer container already exists."
434 if ! docker ps --format '{{.Names}}' | grep -Fxq portainer; then
435 log "Starting existing Portainer container..."
436 docker start portainer
437 fi
438 log "Portainer is available at https://localhost:9443"
439 return 0
440 fi
441
442 docker volume create portainer_data || {
443 err "Failed to create the Portainer data volume."
444 return 1
445 }
446
447 if ! docker run -d \
448 -p 8000:8000 \
449 -p 9443:9443 \
450 --name portainer \
451 --restart=always \
452 -v /var/run/docker.sock:/var/run/docker.sock \
453 -v portainer_data:/data \
454 portainer/portainer-ce:sts; then
455 err "Failed to start the Portainer container."
456 return 1
457 fi
458
459 log "Portainer is available at https://localhost:9443"
460}
461
462install_aws() {
463 if require_cmd aws; then warn "AWS CLI exists"; return; fi
464 log "Installing AWS CLI directly from Amazon..."
465
466 if [[ "$OS" == "macOS" ]]; then
467 curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg"
468 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
469 rm /tmp/AWSCLIV2.pkg
470 else
471 if ! require_cmd unzip; then
472 err "unzip is required. Please install 'Build Tools' (Option 1) first."
473 return 1
474 fi
475 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip"
476 cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install
477 rm -rf /tmp/awscliv2.zip /tmp/aws
478 fi
479}
480
481install_gcloud() {
482 local gcloud_dir="/opt/google-cloud-sdk"
483
484 if [[ -x "$gcloud_dir/bin/gcloud" ]]; then warn "Google Cloud CLI exists at $gcloud_dir"; return; fi
485 log "Installing Google Cloud CLI to $gcloud_dir..."
486 curl -fsSL https://sdk.cloud.google.com | sudo bash -s -- --disable-prompts --install-dir=/opt
487 sudo chown -R "$(id -u):$(id -g)" "$gcloud_dir"
488
489 if [[ "$CURRENT_SHELL" == "zsh" ]]; then
490 add_to_path_config "GCLOUD" '[ -f "/opt/google-cloud-sdk/path.zsh.inc" ] && source "/opt/google-cloud-sdk/path.zsh.inc"'
491 else
492 add_to_path_config "GCLOUD" '[ -f "/opt/google-cloud-sdk/path.bash.inc" ] && source "/opt/google-cloud-sdk/path.bash.inc"'
493 fi
494}
495
496install_firebase() {
497 if require_cmd firebase; then warn "Firebase CLI exists"; return; fi
498 log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..."
499
500 if ! require_cmd npm; then
501 err "NPM not found. Please install NVM (Option 3) first."
502 return 1
503 fi
504
505 npm install -g firebase-tools
506}
507
508# =======================================================
509# DEV TOOLS & SECURITY
510# =======================================================
511
512install_gh() {
513 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
514 log "Fetching latest GitHub CLI version..."
515 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
516 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
517
518 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz"
519 tar -xzf /tmp/gh.tar.gz -C /tmp
520 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
521 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
522}
523
524install_infisical() {
525 if require_cmd infisical; then warn "Infisical CLI exists"; return; fi
526 log "Installing Infisical CLI via NPM..."
527
528 if ! require_cmd npm; then
529 err "NPM not found. Please install NVM (Option 3) first."
530 return 1
531 fi
532
533 npm install -g @infisical/cli
534}
535
536# =======================================================
537# AI & AGENTIC TOOLS
538# =======================================================
539
540install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
541
542install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
543
544install_codex() {
545 log "Installing @openai/codex..."
546 if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi
547 npm i -g @openai/codex
548}
549
550update_all() {
551 log "Updating all installed software from official sources..."
552
553 if require_cmd apt-get; then
554 sudo apt-get update && sudo apt-get install -y --only-upgrade \
555 build-essential curl wget git jq unzip libssl-dev zlib1g-dev libffi-dev libsqlite3-dev \
556 python3 python3-pip python3-venv
557 elif require_cmd dnf; then
558 sudo dnf upgrade -y python3 python3-pip
559 elif require_cmd zypper; then
560 sudo zypper --non-interactive update python3 python3-pip
561 elif require_cmd pacman; then
562 sudo pacman -Syu --noconfirm python python-pip
563 elif require_cmd apk; then
564 sudo apk upgrade
565 fi
566
567 if [[ "$OS" == "macOS" ]] && require_cmd python3; then
568 install_python
569 fi
570
571 require_cmd brew && { brew update && brew upgrade; }
572
573 if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
574 set +u
575 export NVM_DIR="$HOME/.nvm"
576 LATEST_NVM=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
577 curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${LATEST_NVM}/install.sh" | PROFILE=/dev/null $CURRENT_SHELL
578 \. "$NVM_DIR/nvm.sh"
579 nvm install --lts --reinstall-packages-from=current
580 nvm alias default 'lts/*'
581 nvm use --lts
582 set -u
583 fi
584
585 require_cmd go && install_go
586
587 if [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then
588 set +u
589 source "$HOME/.sdkman/bin/sdkman-init.sh"
590 sdk selfupdate force
591 sdk upgrade
592 set -u
593 fi
594
595 if require_cmd dotnet; then
596 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
597 fi
598
599 local sdkmanager="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-$HOME/Android/Sdk}}/cmdline-tools/latest/bin/sdkmanager"
600 if [[ -x "$sdkmanager" ]]; then
601 yes | "$sdkmanager" --licenses >/dev/null || true
602 "$sdkmanager" --update
603 fi
604
605 if require_cmd docker; then
606 if [[ "$OS" == "macOS" ]]; then
607 docker desktop update || warn "Update Docker Desktop from its application menu."
608 else
609 curl -fsSL https://get.docker.com | sudo sh
610 fi
611 fi
612
613 if require_cmd docker && docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
614 log "Updating Portainer CE..."
615 docker pull portainer/portainer-ce:sts && \
616 docker stop portainer && \
617 docker rm portainer && \
618 docker run -d \
619 -p 8000:8000 \
620 -p 9443:9443 \
621 --name portainer \
622 --restart=always \
623 -v /var/run/docker.sock:/var/run/docker.sock \
624 -v portainer_data:/data \
625 portainer/portainer-ce:sts
626 fi
627
628 if require_cmd aws; then
629 if [[ "$OS" == "macOS" ]]; then
630 curl -fsSL -o /tmp/AWSCLIV2.pkg https://awscli.amazonaws.com/AWSCLIV2.pkg && \
631 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
632 rm -f /tmp/AWSCLIV2.pkg
633 elif require_cmd unzip; then
634 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" && \
635 rm -rf /tmp/aws && unzip -q /tmp/awscliv2.zip -d /tmp && \
636 sudo /tmp/aws/install --update
637 rm -rf /tmp/aws /tmp/awscliv2.zip
638 fi
639 fi
640
641 require_cmd gcloud && gcloud components update --quiet
642 require_cmd firebase && npm install -g firebase-tools@latest
643
644 if require_cmd gh; then
645 log "Updating GitHub CLI..."
646 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
647 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
648 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz" && \
649 tar -xzf /tmp/gh.tar.gz -C /tmp && \
650 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
651 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
652 fi
653
654 require_cmd infisical && npm install -g @infisical/cli@latest
655 require_cmd claude && { curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
656 require_cmd codex && npm install -g @openai/codex@latest
657 require_cmd opencode && { curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
658
659 log "Finished updating installed software. Review any warnings or errors above."
660}
661
662# =======================================================
663# MENU LOGIC
664# =======================================================
665show_menu() {
666 clear
667 printf "${BLUE}==================================================${NC}\n"
668 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
669 printf "${BLUE}==================================================${NC}\n"
670 printf "${YELLOW}--- Essentials & Package Managers ---${NC}\n"
671 printf " 1) Build Tools (GCC/Make/Git/Curl/Unzip)\n"
672 printf " 2) Homebrew (Optional)\n"
673 printf "${YELLOW}--- Languages & Runtimes ---${NC}\n"
674 printf " 3) NVM + Node.js\n"
675 printf " 4) Python 3\n"
676 printf " 5) Go\n"
677 printf " 6) SDKMAN (Java/Kotlin/Gradle)\n"
678 printf " 7) .NET\n"
679 printf "${YELLOW}--- Mobile Development ---${NC}\n"
680 printf " 8) Android SDK (CLI + Platform Tools)\n"
681 printf "${YELLOW}--- Containers & Infrastructure ---${NC}\n"
682 printf " 9) Docker\n"
683 printf " 13) Portainer (Docker UI)\n"
684 printf "${YELLOW}--- Cloud & App Platforms ---${NC}\n"
685 printf " 10) AWS CLI\n"
686 printf " 11) Google Cloud CLI (gcloud)\n"
687 printf " 12) Firebase CLI\n"
688 printf "${YELLOW}--- Developer Productivity ---${NC}\n"
689 printf " 14) GitHub CLI\n"
690 printf "${YELLOW}--- Secrets & Security ---${NC}\n"
691 printf " 15) Infisical CLI\n"
692 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
693 printf " 16) Claude Code CLI\n"
694 printf " 17) OpenAI Codex CLI\n"
695 printf " 18) OpenCode\n"
696 printf "${BLUE}==================================================${NC}\n"
697 printf " 19) Update All Installed Software\n"
698 printf " 99) Quit & Refresh Shell\n"
699 printf "${BLUE}==================================================${NC}\n"
700}
701
702while true; do
703 show_menu
704
705 # Bulletproof prompt for both Bash and Zsh
706 printf "Select options (space-separated): "
707 read input
708
709 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
710 for choice in $input; do
711 case "$choice" in
712 1) install_build_tools ;;
713 2) install_brew ;;
714 3) install_nvm ;;
715 4) install_python ;;
716 5) install_go ;;
717 6) install_sdkman ;;
718 7) install_dotnet ;;
719 8) install_android ;;
720 9) install_docker ;;
721 10) install_aws ;;
722 11) install_gcloud ;;
723 12) install_firebase ;;
724 13) install_portainer ;;
725 14) install_gh ;;
726 15) install_infisical ;;
727 16) install_claude ;;
728 17) install_codex ;;
729 18) install_opencode ;;
730 19) update_all ;;
731 99)
732 log "Installation complete! Refreshing terminal environment..."
733
734 # If on Linux, forcefully inherit the new docker group without needing a logout
735 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
736 exec sg docker -c "exec ${SHELL:-zsh}"
737 else
738 exec "${SHELL:-zsh}"
739 fi
740 ;;
741 *) warn "Option $choice not valid." ;;
742 esac
743 done
744
745 printf "Press Enter to continue..."
746 read dummy
747done
748