Ostatnio aktywny 3 weeks ago

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

Rewizja c7daf751fa4f67bcc2babf91053b8790d8b5552e

README.md Surowy

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.
  • Augment Code CLI (Auggie): Installed from Augment's official NPM package; requires Node.js 20 or later.
  • CodeRabbit CLI: Local AI code reviews installed from CodeRabbit's official 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 Surowy
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
550install_augment() {
551 if require_cmd auggie; then warn "Augment Code CLI exists"; return; fi
552 log "Installing Augment Code CLI (Auggie)..."
553 if ! require_cmd npm; then err "Node.js 20+/NPM is required. Install NVM (3) first."; return 1; fi
554 npm install -g @augmentcode/auggie
555}
556
557install_coderabbit() {
558 if require_cmd coderabbit || require_cmd cr; then warn "CodeRabbit CLI exists"; return; fi
559 log "Installing CodeRabbit CLI..."
560 curl -fsSL https://cli.coderabbit.ai/install.sh | sh
561}
562
563update_all() {
564 log "Updating all installed software from official sources..."
565
566 if require_cmd apt-get; then
567 sudo apt-get update && sudo apt-get install -y --only-upgrade \
568 build-essential curl wget git jq unzip libssl-dev zlib1g-dev libffi-dev libsqlite3-dev \
569 python3 python3-pip python3-venv
570 elif require_cmd dnf; then
571 sudo dnf upgrade -y python3 python3-pip
572 elif require_cmd zypper; then
573 sudo zypper --non-interactive update python3 python3-pip
574 elif require_cmd pacman; then
575 sudo pacman -Syu --noconfirm python python-pip
576 elif require_cmd apk; then
577 sudo apk upgrade
578 fi
579
580 if [[ "$OS" == "macOS" ]] && require_cmd python3; then
581 install_python
582 fi
583
584 require_cmd brew && { brew update && brew upgrade; }
585
586 if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
587 set +u
588 export NVM_DIR="$HOME/.nvm"
589 LATEST_NVM=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
590 curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${LATEST_NVM}/install.sh" | PROFILE=/dev/null $CURRENT_SHELL
591 \. "$NVM_DIR/nvm.sh"
592 nvm install --lts --reinstall-packages-from=current
593 nvm alias default 'lts/*'
594 nvm use --lts
595 set -u
596 fi
597
598 require_cmd go && install_go
599
600 if [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then
601 set +u
602 source "$HOME/.sdkman/bin/sdkman-init.sh"
603 sdk selfupdate force
604 sdk upgrade
605 set -u
606 fi
607
608 if require_cmd dotnet; then
609 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
610 fi
611
612 local sdkmanager="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-$HOME/Android/Sdk}}/cmdline-tools/latest/bin/sdkmanager"
613 if [[ -x "$sdkmanager" ]]; then
614 yes | "$sdkmanager" --licenses >/dev/null || true
615 "$sdkmanager" --update
616 fi
617
618 if require_cmd docker; then
619 if [[ "$OS" == "macOS" ]]; then
620 docker desktop update || warn "Update Docker Desktop from its application menu."
621 else
622 curl -fsSL https://get.docker.com | sudo sh
623 fi
624 fi
625
626 if require_cmd docker && docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
627 log "Updating Portainer CE..."
628 docker pull portainer/portainer-ce:sts && \
629 docker stop portainer && \
630 docker rm portainer && \
631 docker run -d \
632 -p 8000:8000 \
633 -p 9443:9443 \
634 --name portainer \
635 --restart=always \
636 -v /var/run/docker.sock:/var/run/docker.sock \
637 -v portainer_data:/data \
638 portainer/portainer-ce:sts
639 fi
640
641 if require_cmd aws; then
642 if [[ "$OS" == "macOS" ]]; then
643 curl -fsSL -o /tmp/AWSCLIV2.pkg https://awscli.amazonaws.com/AWSCLIV2.pkg && \
644 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
645 rm -f /tmp/AWSCLIV2.pkg
646 elif require_cmd unzip; then
647 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" && \
648 rm -rf /tmp/aws && unzip -q /tmp/awscliv2.zip -d /tmp && \
649 sudo /tmp/aws/install --update
650 rm -rf /tmp/aws /tmp/awscliv2.zip
651 fi
652 fi
653
654 require_cmd gcloud && gcloud components update --quiet
655 require_cmd firebase && npm install -g firebase-tools@latest
656
657 if require_cmd gh; then
658 log "Updating GitHub CLI..."
659 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
660 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
661 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz" && \
662 tar -xzf /tmp/gh.tar.gz -C /tmp && \
663 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
664 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
665 fi
666
667 require_cmd infisical && npm install -g @infisical/cli@latest
668 require_cmd claude && { curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
669 require_cmd codex && npm install -g @openai/codex@latest
670 require_cmd opencode && { curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
671 require_cmd auggie && npm install -g @augmentcode/auggie@latest
672 if require_cmd coderabbit || require_cmd cr; then
673 curl -fsSL https://cli.coderabbit.ai/install.sh | sh
674 fi
675
676 log "Finished updating installed software. Review any warnings or errors above."
677}
678
679# =======================================================
680# MENU LOGIC
681# =======================================================
682show_menu() {
683 clear
684 printf "${BLUE}==================================================${NC}\n"
685 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
686 printf "${BLUE}==================================================${NC}\n"
687 printf "${YELLOW}--- Essentials & Package Managers ---${NC}\n"
688 printf " 1) Build Tools (GCC/Make/Git/Curl/Unzip)\n"
689 printf " 2) Homebrew (Optional)\n"
690 printf "${YELLOW}--- Languages & Runtimes ---${NC}\n"
691 printf " 3) NVM + Node.js\n"
692 printf " 4) Python 3\n"
693 printf " 5) Go\n"
694 printf " 6) SDKMAN (Java/Kotlin/Gradle)\n"
695 printf " 7) .NET\n"
696 printf "${YELLOW}--- Mobile Development ---${NC}\n"
697 printf " 8) Android SDK (CLI + Platform Tools)\n"
698 printf "${YELLOW}--- Containers & Infrastructure ---${NC}\n"
699 printf " 9) Docker\n"
700 printf " 13) Portainer (Docker UI)\n"
701 printf "${YELLOW}--- Cloud & App Platforms ---${NC}\n"
702 printf " 10) AWS CLI\n"
703 printf " 11) Google Cloud CLI (gcloud)\n"
704 printf " 12) Firebase CLI\n"
705 printf "${YELLOW}--- Developer Productivity ---${NC}\n"
706 printf " 14) GitHub CLI\n"
707 printf "${YELLOW}--- Secrets & Security ---${NC}\n"
708 printf " 15) Infisical CLI\n"
709 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
710 printf " 16) Claude Code CLI\n"
711 printf " 17) OpenAI Codex CLI\n"
712 printf " 18) OpenCode\n"
713 printf " 20) Augment Code CLI (Auggie)\n"
714 printf " 21) CodeRabbit CLI\n"
715 printf "${BLUE}==================================================${NC}\n"
716 printf " 19) Update All Installed Software\n"
717 printf " 99) Quit & Refresh Shell\n"
718 printf "${BLUE}==================================================${NC}\n"
719}
720
721while true; do
722 show_menu
723
724 # Bulletproof prompt for both Bash and Zsh
725 printf "Select options (space-separated): "
726 read input
727
728 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
729 for choice in $input; do
730 case "$choice" in
731 1) install_build_tools ;;
732 2) install_brew ;;
733 3) install_nvm ;;
734 4) install_python ;;
735 5) install_go ;;
736 6) install_sdkman ;;
737 7) install_dotnet ;;
738 8) install_android ;;
739 9) install_docker ;;
740 10) install_aws ;;
741 11) install_gcloud ;;
742 12) install_firebase ;;
743 13) install_portainer ;;
744 14) install_gh ;;
745 15) install_infisical ;;
746 16) install_claude ;;
747 17) install_codex ;;
748 18) install_opencode ;;
749 19) update_all ;;
750 20) install_augment ;;
751 21) install_coderabbit ;;
752 99)
753 log "Installation complete! Refreshing terminal environment..."
754
755 # If on Linux, forcefully inherit the new docker group without needing a logout
756 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
757 exec sg docker -c "exec ${SHELL:-zsh}"
758 else
759 exec "${SHELL:-zsh}"
760 fi
761 ;;
762 *) warn "Option $choice not valid." ;;
763 esac
764 done
765
766 printf "Press Enter to continue..."
767 read dummy
768done
769