Ostatnio aktywny 3 weeks ago

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

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: Consistently writes installed-tool paths to ~/.zshrc, regardless of which shell launches the installer.
  • 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, restart your terminal or run source ~/.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. SHELL NORMALIZATION
28# =======================================================
29PROFILE_FILE="$HOME/.zshrc"
30
31if [ -n "${ZSH_VERSION:-}" ]; then
32 # Zsh: Enable Bash-like word splitting for unquoted variables (menu input)
33 setopt shwordsplit 2>/dev/null || true
34 CURRENT_SHELL="zsh"
35elif [ -n "${BASH_VERSION:-}" ]; then
36 CURRENT_SHELL="bash"
37else
38 CURRENT_SHELL="sh"
39fi
40
41# =======================================================
42# COLORS & LOGGING
43# =======================================================
44GREEN="\033[0;32m"
45RED="\033[0;31m"
46YELLOW="\033[1;33m"
47BLUE="\033[0;34m"
48NC="\033[0m"
49
50log() { printf "${GREEN}▶ %s${NC}\n" "$*"; }
51warn() { printf "${YELLOW}⚠ %s${NC}\n" "$*"; }
52err() { printf "${RED}✖ %s${NC}\n" "$*"; }
53info() { printf "${BLUE}ℹ %s${NC}\n" "$*"; }
54
55# =======================================================
56# HELPERS & SYSTEM DETECTION
57# =======================================================
58require_cmd() { command -v "$1" >/dev/null 2>&1; }
59
60add_to_path_config() {
61 local label=$1
62 local path_line=$2
63 local target_file="${3:-$PROFILE_FILE}"
64
65 if [[ -f "$target_file" ]]; then
66 if ! grep -q "$label" "$target_file"; then
67 printf "\n# %s\n%s\n" "$label" "$path_line" >> "$target_file"
68 log "Added $label to $target_file"
69 fi
70 else
71 printf "\n# %s\n%s\n" "$label" "$path_line" >> "$PROFILE_FILE"
72 log "Added $label to shell profile ($PROFILE_FILE)."
73 fi
74
75 # Instantly evaluate the path line so the current script session
76 # can immediately use the newly installed tool in subsequent steps.
77 eval "$path_line" 2>/dev/null || true
78}
79
80detect_os_arch() {
81 ARCH=$(uname -m)
82 case "$ARCH" in
83 x86_64|amd64) SYS_ARCH="amd64"; MAC_ARCH="x86_64"; AWS_ARCH="x86_64" ;;
84 aarch64|arm64) SYS_ARCH="arm64"; MAC_ARCH="arm64"; AWS_ARCH="aarch64" ;;
85 *) err "Unsupported architecture: $ARCH"; exit 1 ;;
86 esac
87
88 if [[ "$OSTYPE" == "darwin"* ]]; then
89 OS="macOS"
90 OS_LOWER="darwin"
91 elif grep -qi microsoft /proc/version 2>/dev/null; then
92 OS="WSL"
93 OS_LOWER="linux"
94 elif [[ -f /etc/os-release ]]; then
95 . /etc/os-release
96 [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* ]] && OS="Ubuntu" || OS="Linux"
97 OS_LOWER="linux"
98 else
99 OS="Unknown"
100 OS_LOWER="unknown"
101 fi
102 log "Detected: $OS ($ARCH) running $CURRENT_SHELL"
103}
104
105detect_os_arch
106
107# =======================================================
108# CORE RUNTIMES & MANAGERS
109# =======================================================
110
111install_build_tools() {
112 log "Installing Build Essentials & Core Dependencies..."
113 case "$OS" in
114 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 ;;
115 macOS) xcode-select --install || warn "Xcode tools already installed" ;;
116 *) warn "Manual installation required for $OS." ;;
117 esac
118}
119
120install_brew() {
121 if require_cmd brew; then warn "Homebrew already installed"; return; fi
122 log "Installing Homebrew from Official Source..."
123
124 # Homebrew's installer specifically requires Bash execution, regardless of current shell
125 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
126
127 if [[ "$OS_LOWER" == "linux" ]]; then
128 add_to_path_config "HOMEBREW" 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"'
129 elif [[ "$MAC_ARCH" == "arm64" ]]; then
130 add_to_path_config "HOMEBREW" 'eval "$(/opt/homebrew/bin/brew shellenv)"'
131 else
132 add_to_path_config "HOMEBREW" 'eval "$(/usr/local/bin/brew shellenv)"'
133 fi
134}
135
136install_nvm() {
137 # 1. Temporarily disable 'unbound variable' strictness for NVM
138 set +u
139
140 export NVM_DIR="$HOME/.nvm"
141
142 if [ -d "$NVM_DIR" ]; then
143 warn "NVM is already installed."
144 else
145 log "Installing NVM via $CURRENT_SHELL..."
146 curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | $CURRENT_SHELL
147 fi
148
149 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
150
151 printf "\n${YELLOW}Which version of Node.js would you like to install?${NC}\n"
152 printf " 1) LTS (Long Term Support - Recommended for stability)\n"
153 printf " 2) Latest (Current features - Recommended for testing new APIs)\n"
154 printf " 3) Skip installing Node.js right now\n"
155
156 printf "Select (1/2/3): "
157 read node_choice
158
159 case "$node_choice" in
160 2)
161 log "Installing Latest Node.js..."
162 nvm install node
163 nvm alias default node
164 nvm use node
165
166 log "Disabling npm logs..."
167 npm config set logs-max 0
168 ;;
169 3)
170 log "Skipping Node.js installation."
171 ;;
172 *)
173 log "Installing Latest LTS Node.js..."
174 nvm install --lts
175 nvm alias default 'lts/*'
176 nvm use --lts
177
178 log "Disabling npm logs..."
179 npm config set logs-max 0
180 ;;
181 esac
182
183 # 2. Re-enable 'unbound variable' strictness for the rest of your script
184 set -u
185}
186
187install_python() {
188 if [[ "$OS" == "macOS" ]]; then
189 log "Fetching latest stable Python version..."
190 LATEST_PY=$(
191 curl -fsSL --compressed https://www.python.org/downloads/ |
192 sed -nE 's/.*Download Python ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' |
193 head -1
194 )
195 if [[ -z "$LATEST_PY" ]]; then
196 err "Could not detect the latest stable Python version from python.org."
197 return 1
198 fi
199
200 log "Detected Stable Version: $LATEST_PY"
201 log "Downloading official macOS package..."
202 PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg"
203 curl -fsSL --compressed -o /tmp/python.pkg "$PKG_URL" || {
204 err "Failed to download Python macOS package from $PKG_URL"
205 return 1
206 }
207
208 log "Running installer..."
209 sudo installer -pkg /tmp/python.pkg -target / || {
210 err "Python macOS installer failed."
211 rm -f /tmp/python.pkg
212 return 1
213 }
214 rm /tmp/python.pkg
215 else
216 log "Installing precompiled Python from the official OS repository..."
217 if require_cmd apt-get; then
218 sudo apt-get update && \
219 sudo apt-get install -y python3 python3-pip python3-venv
220 elif require_cmd dnf; then
221 sudo dnf install -y python3 python3-pip
222 elif require_cmd zypper; then
223 sudo zypper --non-interactive install python3 python3-pip
224 elif require_cmd pacman; then
225 sudo pacman -Sy --needed python python-pip
226 elif require_cmd apk; then
227 sudo apk add python3 py3-pip py3-virtualenv
228 else
229 err "No supported official OS package manager found for Python."
230 return 1
231 fi
232 fi
233
234 require_cmd python3 || { err "python3 was not installed successfully."; return 1; }
235 python3 -m pip --version >/dev/null 2>&1 || {
236 err "pip is unavailable for the installed Python."
237 return 1
238 }
239 python3 -m venv --help >/dev/null 2>&1 || {
240 err "venv is unavailable for the installed Python."
241 return 1
242 }
243
244 log "$(python3 --version) installed with pip and venv support."
245}
246
247install_go() {
248 log "Fetching latest Go version from official source..."
249 LATEST_GO=$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -n 1) || {
250 err "Could not determine the latest Go version."
251 return 1
252 }
253 TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz"
254
255 if [[ -x /usr/local/go/bin/go ]] && [[ "$(/usr/local/go/bin/go version)" == "go version $LATEST_GO "* ]]; then
256 warn "$LATEST_GO is already installed."
257 add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"'
258 return 0
259 fi
260
261 log "Downloading $TAR_FILE..."
262 curl -fsSL -o /tmp/go.tar.gz "https://go.dev/dl/$TAR_FILE" || {
263 err "Failed to download $TAR_FILE."
264 rm -f /tmp/go.tar.gz
265 return 1
266 }
267
268 log "Installing to /usr/local/go..."
269 if ! sudo rm -rf /usr/local/go || ! sudo tar -C /usr/local -xzf /tmp/go.tar.gz; then
270 err "Go installation failed. Run this installer from an interactive terminal so sudo can authenticate."
271 rm -f /tmp/go.tar.gz
272 return 1
273 fi
274 rm -f /tmp/go.tar.gz
275 add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"'
276 log "$(/usr/local/go/bin/go version) installed successfully."
277}
278
279install_sdkman() {
280 if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi
281
282 # SDKMAN explicitly blocks macOS's default Bash 3.2.
283 # If we are trapped in Bash < 4, force the installer to use Zsh.
284 if [[ "$CURRENT_SHELL" == "bash" && "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then
285 log "Outdated Bash detected. Installing SDKMAN via Zsh..."
286 curl -s "https://get.sdkman.io" | zsh
287 else
288 log "Installing SDKMAN via $CURRENT_SHELL..."
289 curl -s "https://get.sdkman.io" | $CURRENT_SHELL
290 fi
291}
292
293install_dotnet() {
294 log "Installing .NET from official script..."
295 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
296 add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"'
297}
298
299install_android() {
300 log "Installing Android SDK command-line tools..."
301
302 if ! require_cmd unzip; then
303 err "unzip is required. Please install 'Build Tools' (Option 1) first."
304 return 1
305 fi
306
307 if [[ "$OS_LOWER" == "linux" && "$SYS_ARCH" != "amd64" ]]; then
308 err "Google's Android command-line tools installer supports Linux x86_64 only."
309 return 1
310 fi
311
312 ANDROID_HOME="$HOME/Android/Sdk"
313 ANDROID_SDK_ROOT="$ANDROID_HOME"
314 export ANDROID_HOME ANDROID_SDK_ROOT
315
316 mkdir -p "$ANDROID_HOME/cmdline-tools"
317
318 if [[ "$OS" == "macOS" ]]; then
319 TOOLS_OS="mac"
320 elif [[ "$OS_LOWER" == "linux" ]]; then
321 TOOLS_OS="linux"
322 else
323 err "Android SDK installation is not supported for $OS."
324 return 1
325 fi
326
327 log "Finding latest Android command-line tools from Google..."
328 TOOLS_URL=$(curl -fsSL https://developer.android.com/studio \
329 | grep -o "https://dl.google.com/android/repository/commandlinetools-${TOOLS_OS}-[0-9]*_latest.zip" \
330 | head -1)
331
332 if [[ -z "$TOOLS_URL" ]]; then
333 err "Could not find the Android command-line tools download URL."
334 return 1
335 fi
336
337 log "Downloading Android command-line tools..."
338 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
339 mkdir -p /tmp/android-cmdline-tools
340 curl -fsSL -o /tmp/android-cmdline-tools.zip "$TOOLS_URL"
341 unzip -q /tmp/android-cmdline-tools.zip -d /tmp/android-cmdline-tools
342
343 rm -rf "$ANDROID_HOME/cmdline-tools/latest"
344 mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
345 mv /tmp/android-cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
346 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
347
348 SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
349 if [[ ! -x "$SDKMANAGER" ]]; then
350 err "sdkmanager was not installed correctly."
351 return 1
352 fi
353
354 log "Accepting Android SDK licenses..."
355 yes | "$SDKMANAGER" --sdk_root="$ANDROID_HOME" --licenses >/dev/null || true
356
357 log "Installing Android SDK packages..."
358 "$SDKMANAGER" --sdk_root="$ANDROID_HOME" \
359 "cmdline-tools;latest" \
360 "platform-tools" \
361 "emulator" \
362 "platforms;android-36" \
363 "build-tools;36.0.0"
364
365 add_to_path_config "ANDROID_SDK" 'export ANDROID_HOME="$HOME/Android/Sdk"
366export ANDROID_SDK_ROOT="$ANDROID_HOME"
367export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"'
368
369 log "Android SDK installation sequence finished!"
370}
371
372# =======================================================
373# CLOUD & INFRASTRUCTURE
374# =======================================================
375
376install_docker() {
377 log "Checking Docker status..."
378
379 # 1. Install if missing
380 if ! require_cmd docker; then
381 log "Installing Docker from official source..."
382 if [[ "$OS" == "macOS" ]]; then
383 [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64"
384 DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg"
385 curl -fsSL -o /tmp/Docker.dmg "$DMG_URL"
386 hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker
387 sudo cp -a /Volumes/Docker/Docker.app /Applications/
388 hdiutil detach /Volumes/Docker
389 rm /tmp/Docker.dmg
390
391 log "Starting Docker Desktop..."
392 open /Applications/Docker.app
393 log "Please complete the setup in the Docker Desktop UI."
394 else
395 curl -fsSL https://get.docker.com | sudo sh
396 fi
397 else
398 warn "Docker is already installed. Enforcing permissions..."
399 fi
400
401 # 2. Aggressively enforce Linux permissions
402 if [[ "$OS" != "macOS" ]]; then
403 log "Applying strict Linux post-install actions..."
404
405 # Ensure docker group exists and user is added
406 sudo groupadd -f docker
407 sudo usermod -aG docker "$USER"
408
409 # Ensure services are enabled and running
410 if command -v systemctl >/dev/null 2>&1; then
411 sudo systemctl enable --now docker.service
412 sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true
413 elif [[ "$OS" == "WSL" ]]; then
414 sudo service docker start
415 fi
416
417 # Give the system a second to generate the socket file
418 sleep 2
419
420 # Lock in ownership and permissions on the socket
421 if [[ -S /var/run/docker.sock ]]; then
422 log "Securing /var/run/docker.sock..."
423 sudo chown root:docker /var/run/docker.sock
424 sudo chmod 660 /var/run/docker.sock
425 else
426 warn "Docker socket not found. The service may have failed to start."
427 fi
428
429 log "Added $USER to the docker group and secured the socket."
430 fi
431}
432
433install_portainer() {
434 log "Installing Portainer CE..."
435
436 if ! require_cmd docker; then
437 err "Docker is required. Please install Docker (Option 9) first."
438 return 1
439 fi
440
441 if ! docker info >/dev/null 2>&1; then
442 err "Docker is installed but the daemon is not running. Start Docker, then retry Portainer."
443 return 1
444 fi
445
446 if docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
447 warn "Portainer container already exists."
448 if ! docker ps --format '{{.Names}}' | grep -Fxq portainer; then
449 log "Starting existing Portainer container..."
450 docker start portainer
451 fi
452 log "Portainer is available at https://localhost:9443"
453 return 0
454 fi
455
456 docker volume create portainer_data || {
457 err "Failed to create the Portainer data volume."
458 return 1
459 }
460
461 if ! docker run -d \
462 -p 8000:8000 \
463 -p 9443:9443 \
464 --name portainer \
465 --restart=always \
466 -v /var/run/docker.sock:/var/run/docker.sock \
467 -v portainer_data:/data \
468 portainer/portainer-ce:sts; then
469 err "Failed to start the Portainer container."
470 return 1
471 fi
472
473 log "Portainer is available at https://localhost:9443"
474}
475
476install_aws() {
477 if require_cmd aws; then warn "AWS CLI exists"; return; fi
478 log "Installing AWS CLI directly from Amazon..."
479
480 if [[ "$OS" == "macOS" ]]; then
481 curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg"
482 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
483 rm /tmp/AWSCLIV2.pkg
484 else
485 if ! require_cmd unzip; then
486 err "unzip is required. Please install 'Build Tools' (Option 1) first."
487 return 1
488 fi
489 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip"
490 cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install
491 rm -rf /tmp/awscliv2.zip /tmp/aws
492 fi
493}
494
495install_gcloud() {
496 local gcloud_dir="/opt/google-cloud-sdk"
497
498 if [[ -x "$gcloud_dir/bin/gcloud" ]]; then warn "Google Cloud CLI exists at $gcloud_dir"; return; fi
499 log "Installing Google Cloud CLI to $gcloud_dir..."
500 curl -fsSL https://sdk.cloud.google.com | sudo bash -s -- --disable-prompts --install-dir=/opt
501 sudo chown -R "$(id -u):$(id -g)" "$gcloud_dir"
502
503 if [[ "$CURRENT_SHELL" == "zsh" ]]; then
504 add_to_path_config "GCLOUD" '[ -f "/opt/google-cloud-sdk/path.zsh.inc" ] && source "/opt/google-cloud-sdk/path.zsh.inc"'
505 else
506 add_to_path_config "GCLOUD" '[ -f "/opt/google-cloud-sdk/path.bash.inc" ] && source "/opt/google-cloud-sdk/path.bash.inc"'
507 fi
508}
509
510install_firebase() {
511 if require_cmd firebase; then warn "Firebase CLI exists"; return; fi
512 log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..."
513
514 if ! require_cmd npm; then
515 err "NPM not found. Please install NVM (Option 3) first."
516 return 1
517 fi
518
519 npm install -g firebase-tools
520}
521
522# =======================================================
523# DEV TOOLS & SECURITY
524# =======================================================
525
526install_gh() {
527 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
528 log "Fetching latest GitHub CLI version..."
529 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
530 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
531
532 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz"
533 tar -xzf /tmp/gh.tar.gz -C /tmp
534 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
535 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
536}
537
538install_infisical() {
539 if require_cmd infisical; then warn "Infisical CLI exists"; return; fi
540 log "Installing Infisical CLI via NPM..."
541
542 if ! require_cmd npm; then
543 err "NPM not found. Please install NVM (Option 3) first."
544 return 1
545 fi
546
547 npm install -g @infisical/cli
548}
549
550# =======================================================
551# AI & AGENTIC TOOLS
552# =======================================================
553
554install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
555
556install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
557
558install_codex() {
559 log "Installing @openai/codex..."
560 if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi
561 npm i -g @openai/codex
562}
563
564install_augment() {
565 if require_cmd auggie; then warn "Augment Code CLI exists"; return; fi
566 log "Installing Augment Code CLI (Auggie)..."
567 if ! require_cmd npm; then err "Node.js 20+/NPM is required. Install NVM (3) first."; return 1; fi
568 npm install -g @augmentcode/auggie
569}
570
571install_coderabbit() {
572 if require_cmd coderabbit || require_cmd cr; then warn "CodeRabbit CLI exists"; return; fi
573 log "Installing CodeRabbit CLI..."
574 curl -fsSL https://cli.coderabbit.ai/install.sh | sh
575}
576
577update_all() {
578 log "Updating all installed software from official sources..."
579
580 if require_cmd apt-get; then
581 sudo apt-get update && sudo apt-get install -y --only-upgrade \
582 build-essential curl wget git jq unzip libssl-dev zlib1g-dev libffi-dev libsqlite3-dev \
583 python3 python3-pip python3-venv
584 elif require_cmd dnf; then
585 sudo dnf upgrade -y python3 python3-pip
586 elif require_cmd zypper; then
587 sudo zypper --non-interactive update python3 python3-pip
588 elif require_cmd pacman; then
589 sudo pacman -Syu --noconfirm python python-pip
590 elif require_cmd apk; then
591 sudo apk upgrade
592 fi
593
594 if [[ "$OS" == "macOS" ]] && require_cmd python3; then
595 install_python
596 fi
597
598 require_cmd brew && { brew update && brew upgrade; }
599
600 if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
601 set +u
602 export NVM_DIR="$HOME/.nvm"
603 LATEST_NVM=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
604 curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${LATEST_NVM}/install.sh" | PROFILE=/dev/null $CURRENT_SHELL
605 \. "$NVM_DIR/nvm.sh"
606 nvm install --lts --reinstall-packages-from=current
607 nvm alias default 'lts/*'
608 nvm use --lts
609 set -u
610 fi
611
612 require_cmd go && install_go
613
614 if [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then
615 set +u
616 source "$HOME/.sdkman/bin/sdkman-init.sh"
617 sdk selfupdate force
618 sdk upgrade
619 set -u
620 fi
621
622 if require_cmd dotnet; then
623 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
624 fi
625
626 local sdkmanager="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-$HOME/Android/Sdk}}/cmdline-tools/latest/bin/sdkmanager"
627 if [[ -x "$sdkmanager" ]]; then
628 yes | "$sdkmanager" --licenses >/dev/null || true
629 "$sdkmanager" --update
630 fi
631
632 if require_cmd docker; then
633 if [[ "$OS" == "macOS" ]]; then
634 docker desktop update || warn "Update Docker Desktop from its application menu."
635 else
636 curl -fsSL https://get.docker.com | sudo sh
637 fi
638 fi
639
640 if require_cmd docker && docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
641 log "Updating Portainer CE..."
642 docker pull portainer/portainer-ce:sts && \
643 docker stop portainer && \
644 docker rm portainer && \
645 docker run -d \
646 -p 8000:8000 \
647 -p 9443:9443 \
648 --name portainer \
649 --restart=always \
650 -v /var/run/docker.sock:/var/run/docker.sock \
651 -v portainer_data:/data \
652 portainer/portainer-ce:sts
653 fi
654
655 if require_cmd aws; then
656 if [[ "$OS" == "macOS" ]]; then
657 curl -fsSL -o /tmp/AWSCLIV2.pkg https://awscli.amazonaws.com/AWSCLIV2.pkg && \
658 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
659 rm -f /tmp/AWSCLIV2.pkg
660 elif require_cmd unzip; then
661 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" && \
662 rm -rf /tmp/aws && unzip -q /tmp/awscliv2.zip -d /tmp && \
663 sudo /tmp/aws/install --update
664 rm -rf /tmp/aws /tmp/awscliv2.zip
665 fi
666 fi
667
668 require_cmd gcloud && gcloud components update --quiet
669 require_cmd firebase && npm install -g firebase-tools@latest
670
671 if require_cmd gh; then
672 log "Updating GitHub CLI..."
673 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
674 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
675 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz" && \
676 tar -xzf /tmp/gh.tar.gz -C /tmp && \
677 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
678 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
679 fi
680
681 require_cmd infisical && npm install -g @infisical/cli@latest
682 require_cmd claude && { curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
683 require_cmd codex && npm install -g @openai/codex@latest
684 require_cmd opencode && { curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
685 require_cmd auggie && npm install -g @augmentcode/auggie@latest
686 if require_cmd coderabbit || require_cmd cr; then
687 curl -fsSL https://cli.coderabbit.ai/install.sh | sh
688 fi
689
690 log "Finished updating installed software. Review any warnings or errors above."
691}
692
693# =======================================================
694# MENU LOGIC
695# =======================================================
696show_menu() {
697 clear
698 printf "${BLUE}==================================================${NC}\n"
699 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
700 printf "${BLUE}==================================================${NC}\n"
701 printf "${YELLOW}--- Essentials & Package Managers ---${NC}\n"
702 printf " 1) Build Tools (GCC/Make/Git/Curl/Unzip)\n"
703 printf " 2) Homebrew (Optional)\n"
704 printf "${YELLOW}--- Languages & Runtimes ---${NC}\n"
705 printf " 3) NVM + Node.js\n"
706 printf " 4) Python 3\n"
707 printf " 5) Go\n"
708 printf " 6) SDKMAN (Java/Kotlin/Gradle)\n"
709 printf " 7) .NET\n"
710 printf "${YELLOW}--- Mobile Development ---${NC}\n"
711 printf " 8) Android SDK (CLI + Platform Tools)\n"
712 printf "${YELLOW}--- Containers & Infrastructure ---${NC}\n"
713 printf " 9) Docker\n"
714 printf " 13) Portainer (Docker UI)\n"
715 printf "${YELLOW}--- Cloud & App Platforms ---${NC}\n"
716 printf " 10) AWS CLI\n"
717 printf " 11) Google Cloud CLI (gcloud)\n"
718 printf " 12) Firebase CLI\n"
719 printf "${YELLOW}--- Developer Productivity ---${NC}\n"
720 printf " 14) GitHub CLI\n"
721 printf "${YELLOW}--- Secrets & Security ---${NC}\n"
722 printf " 15) Infisical CLI\n"
723 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
724 printf " 16) Claude Code CLI\n"
725 printf " 17) OpenAI Codex CLI\n"
726 printf " 18) OpenCode\n"
727 printf " 20) Augment Code CLI (Auggie)\n"
728 printf " 21) CodeRabbit CLI\n"
729 printf "${BLUE}==================================================${NC}\n"
730 printf " 19) Update All Installed Software\n"
731 printf " 99) Quit & Refresh Shell\n"
732 printf "${BLUE}==================================================${NC}\n"
733}
734
735while true; do
736 show_menu
737
738 # Bulletproof prompt for both Bash and Zsh
739 printf "Select options (space-separated): "
740 if ! read input; then
741 printf "\n"
742 warn "Input closed. Exiting installer."
743 exit 0
744 fi
745
746 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
747 for choice in $input; do
748 case "$choice" in
749 1) install_build_tools ;;
750 2) install_brew ;;
751 3) install_nvm ;;
752 4) install_python ;;
753 5) install_go ;;
754 6) install_sdkman ;;
755 7) install_dotnet ;;
756 8) install_android ;;
757 9) install_docker ;;
758 10) install_aws ;;
759 11) install_gcloud ;;
760 12) install_firebase ;;
761 13) install_portainer ;;
762 14) install_gh ;;
763 15) install_infisical ;;
764 16) install_claude ;;
765 17) install_codex ;;
766 18) install_opencode ;;
767 19) update_all ;;
768 20) install_augment ;;
769 21) install_coderabbit ;;
770 99)
771 log "Installation complete! Refreshing terminal environment..."
772
773 # If on Linux, forcefully inherit the new docker group without needing a logout
774 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
775 exec sg docker -c "exec ${SHELL:-zsh}"
776 else
777 exec "${SHELL:-zsh}"
778 fi
779 ;;
780 *) warn "Option $choice not valid." ;;
781 esac
782 done
783
784 printf "Press Enter to continue..."
785 read dummy || exit 0
786done
787