最終更新 3 weeks ago

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

修正履歴 2429862e1d637933ea55bd57d9d6ae449773e804

README.md Raw

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).

🛠️ 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 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 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
550# =======================================================
551# MENU LOGIC
552# =======================================================
553show_menu() {
554 clear
555 printf "${BLUE}==================================================${NC}\n"
556 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
557 printf "${BLUE}==================================================${NC}\n"
558 printf "${YELLOW}--- Essentials & Package Managers ---${NC}\n"
559 printf " 1) Build Tools (GCC/Make/Git/Curl/Unzip)\n"
560 printf " 2) Homebrew (Optional)\n"
561 printf "${YELLOW}--- Languages & Runtimes ---${NC}\n"
562 printf " 3) NVM + Node.js\n"
563 printf " 4) Python 3\n"
564 printf " 5) Go\n"
565 printf " 6) SDKMAN (Java/Kotlin/Gradle)\n"
566 printf " 7) .NET\n"
567 printf "${YELLOW}--- Mobile Development ---${NC}\n"
568 printf " 8) Android SDK (CLI + Platform Tools)\n"
569 printf "${YELLOW}--- Containers & Infrastructure ---${NC}\n"
570 printf " 9) Docker\n"
571 printf " 13) Portainer (Docker UI)\n"
572 printf "${YELLOW}--- Cloud & App Platforms ---${NC}\n"
573 printf " 10) AWS CLI\n"
574 printf " 11) Google Cloud CLI (gcloud)\n"
575 printf " 12) Firebase CLI\n"
576 printf "${YELLOW}--- Developer Productivity ---${NC}\n"
577 printf " 14) GitHub CLI\n"
578 printf "${YELLOW}--- Secrets & Security ---${NC}\n"
579 printf " 15) Infisical CLI\n"
580 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
581 printf " 16) Claude Code CLI\n"
582 printf " 17) OpenAI Codex CLI\n"
583 printf " 18) OpenCode\n"
584 printf "${BLUE}==================================================${NC}\n"
585 printf " 99) Quit & Refresh Shell\n"
586 printf "${BLUE}==================================================${NC}\n"
587}
588
589while true; do
590 show_menu
591
592 # Bulletproof prompt for both Bash and Zsh
593 printf "Select options (space-separated): "
594 read input
595
596 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
597 for choice in $input; do
598 case "$choice" in
599 1) install_build_tools ;;
600 2) install_brew ;;
601 3) install_nvm ;;
602 4) install_python ;;
603 5) install_go ;;
604 6) install_sdkman ;;
605 7) install_dotnet ;;
606 8) install_android ;;
607 9) install_docker ;;
608 10) install_aws ;;
609 11) install_gcloud ;;
610 12) install_firebase ;;
611 13) install_portainer ;;
612 14) install_gh ;;
613 15) install_infisical ;;
614 16) install_claude ;;
615 17) install_codex ;;
616 18) install_opencode ;;
617 99)
618 log "Installation complete! Refreshing terminal environment..."
619
620 # If on Linux, forcefully inherit the new docker group without needing a logout
621 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
622 exec sg docker -c "exec ${SHELL:-zsh}"
623 else
624 exec "${SHELL:-zsh}"
625 fi
626 ;;
627 *) warn "Option $choice not valid." ;;
628 esac
629 done
630
631 printf "Press Enter to continue..."
632 read dummy
633done
634