最終更新 3 weeks ago

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

修正履歴 96332971b62ecb365f992d53a52ccaaa66e0e73c

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

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

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

⚠️ 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_portainer() {
441 log "Installing Portainer CE..."
442
443 if ! require_cmd docker; then
444 err "Docker is required. Please install Docker (Option 9) first."
445 return 1
446 fi
447
448 if ! docker info >/dev/null 2>&1; then
449 err "Docker is installed but the daemon is not running. Start Docker, then retry Portainer."
450 return 1
451 fi
452
453 if docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
454 warn "Portainer container already exists."
455 if ! docker ps --format '{{.Names}}' | grep -Fxq portainer; then
456 log "Starting existing Portainer container..."
457 docker start portainer
458 fi
459 log "Portainer is available at https://localhost:9443"
460 return 0
461 fi
462
463 docker volume create portainer_data || {
464 err "Failed to create the Portainer data volume."
465 return 1
466 }
467
468 if ! docker run -d \
469 -p 8000:8000 \
470 -p 9443:9443 \
471 --name portainer \
472 --restart=always \
473 -v /var/run/docker.sock:/var/run/docker.sock \
474 -v portainer_data:/data \
475 portainer/portainer-ce:sts; then
476 err "Failed to start the Portainer container."
477 return 1
478 fi
479
480 log "Portainer is available at https://localhost:9443"
481}
482
483install_aws() {
484 if require_cmd aws; then warn "AWS CLI exists"; return; fi
485 log "Installing AWS CLI directly from Amazon..."
486
487 if [[ "$OS" == "macOS" ]]; then
488 curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg"
489 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
490 rm /tmp/AWSCLIV2.pkg
491 else
492 if ! require_cmd unzip; then
493 err "unzip is required. Please install 'Build Tools' (Option 1) first."
494 return 1
495 fi
496 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip"
497 cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install
498 rm -rf /tmp/awscliv2.zip /tmp/aws
499 fi
500}
501
502install_gcloud() {
503 if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi
504 log "Installing Google Cloud CLI..."
505 curl -fsSL https://sdk.cloud.google.com | $CURRENT_SHELL -s -- --disable-prompts
506
507 if [[ "$CURRENT_SHELL" == "zsh" ]]; then
508 add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc"'
509 else
510 add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && source "$HOME/google-cloud-sdk/path.bash.inc"'
511 fi
512}
513
514install_firebase() {
515 if require_cmd firebase; then warn "Firebase CLI exists"; return; fi
516 log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..."
517
518 if ! require_cmd npm; then
519 err "NPM not found. Please install NVM (Option 3) first."
520 return 1
521 fi
522
523 npm install -g firebase-tools
524}
525
526# =======================================================
527# DEV TOOLS & SECURITY
528# =======================================================
529
530install_gh() {
531 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
532 log "Fetching latest GitHub CLI version..."
533 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
534 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
535
536 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz"
537 tar -xzf /tmp/gh.tar.gz -C /tmp
538 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
539 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
540}
541
542install_infisical() {
543 if require_cmd infisical; then warn "Infisical CLI exists"; return; fi
544 log "Installing Infisical CLI via NPM..."
545
546 if ! require_cmd npm; then
547 err "NPM not found. Please install NVM (Option 3) first."
548 return 1
549 fi
550
551 npm install -g @infisical/cli
552}
553
554# =======================================================
555# AI & AGENTIC TOOLS
556# =======================================================
557
558install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
559
560install_codex() {
561 log "Installing @openai/codex..."
562 if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi
563 npm i -g @openai/codex
564}
565
566# =======================================================
567# MENU LOGIC
568# =======================================================
569show_menu() {
570 clear
571 printf "${BLUE}==================================================${NC}\n"
572 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
573 printf "${BLUE}==================================================${NC}\n"
574 printf "${YELLOW}--- Essentials & Package Managers ---${NC}\n"
575 printf " 1) Build Tools (GCC/Make/Git/Curl/Unzip)\n"
576 printf " 2) Homebrew (Optional)\n"
577 printf "${YELLOW}--- Languages & Runtimes ---${NC}\n"
578 printf " 3) NVM + Node.js\n"
579 printf " 4) Python 3\n"
580 printf " 5) Go\n"
581 printf " 6) SDKMAN (Java/Kotlin/Gradle)\n"
582 printf " 7) .NET\n"
583 printf "${YELLOW}--- Mobile Development ---${NC}\n"
584 printf " 8) Android SDK (CLI + Platform Tools)\n"
585 printf "${YELLOW}--- Containers & Infrastructure ---${NC}\n"
586 printf " 9) Docker\n"
587 printf " 13) Portainer (Docker UI)\n"
588 printf "${YELLOW}--- Cloud & App Platforms ---${NC}\n"
589 printf " 10) AWS CLI\n"
590 printf " 11) Google Cloud CLI (gcloud)\n"
591 printf " 12) Firebase CLI\n"
592 printf "${YELLOW}--- Developer Productivity ---${NC}\n"
593 printf " 14) GitHub CLI\n"
594 printf "${YELLOW}--- Secrets & Security ---${NC}\n"
595 printf " 15) Infisical CLI\n"
596 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
597 printf " 16) Claude Code CLI\n"
598 printf " 17) OpenAI Codex CLI\n"
599 printf "${BLUE}==================================================${NC}\n"
600 printf " 99) Quit & Refresh Shell\n"
601 printf "${BLUE}==================================================${NC}\n"
602}
603
604while true; do
605 show_menu
606
607 # Bulletproof prompt for both Bash and Zsh
608 printf "Select options (space-separated): "
609 read input
610
611 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
612 for choice in $input; do
613 case "$choice" in
614 1) install_build_tools ;;
615 2) install_brew ;;
616 3) install_nvm ;;
617 4) install_python ;;
618 5) install_go ;;
619 6) install_sdkman ;;
620 7) install_dotnet ;;
621 8) install_android ;;
622 9) install_docker ;;
623 10) install_aws ;;
624 11) install_gcloud ;;
625 12) install_firebase ;;
626 13) install_portainer ;;
627 14) install_gh ;;
628 15) install_infisical ;;
629 16) install_claude ;;
630 17) install_codex ;;
631 99)
632 log "Installation complete! Refreshing terminal environment..."
633
634 # If on Linux, forcefully inherit the new docker group without needing a logout
635 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
636 exec sg docker -c "exec ${SHELL:-zsh}"
637 else
638 exec "${SHELL:-zsh}"
639 fi
640 ;;
641 *) warn "Option $choice not valid." ;;
642 esac
643 done
644
645 printf "Press Enter to continue..."
646 read dummy
647done
648