最終更新 6 days ago

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

修正履歴 728946db0f7a7f4a1a4ce807b87dba77eb290aa1

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: 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 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. 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
299# =======================================================
300# CLOUD & INFRASTRUCTURE
301# =======================================================
302
303install_docker() {
304 log "Checking Docker status..."
305
306 # 1. Install if missing
307 if ! require_cmd docker; then
308 log "Installing Docker from official source..."
309 if [[ "$OS" == "macOS" ]]; then
310 [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64"
311 DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg"
312 curl -fsSL -o /tmp/Docker.dmg "$DMG_URL"
313 hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker
314 sudo cp -a /Volumes/Docker/Docker.app /Applications/
315 hdiutil detach /Volumes/Docker
316 rm /tmp/Docker.dmg
317
318 log "Starting Docker Desktop..."
319 open /Applications/Docker.app
320 log "Please complete the setup in the Docker Desktop UI."
321 else
322 curl -fsSL https://get.docker.com | sudo sh
323 fi
324 else
325 warn "Docker is already installed. Enforcing permissions..."
326 fi
327
328 # 2. Aggressively enforce Linux permissions
329 if [[ "$OS" != "macOS" ]]; then
330 log "Applying strict Linux post-install actions..."
331
332 # Ensure docker group exists and user is added
333 sudo groupadd -f docker
334 sudo usermod -aG docker "$USER"
335
336 # Ensure services are enabled and running
337 if command -v systemctl >/dev/null 2>&1; then
338 sudo systemctl enable --now docker.service
339 sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true
340 elif [[ "$OS" == "WSL" ]]; then
341 sudo service docker start
342 fi
343
344 # Give the system a second to generate the socket file
345 sleep 2
346
347 # Lock in ownership and permissions on the socket
348 if [[ -S /var/run/docker.sock ]]; then
349 log "Securing /var/run/docker.sock..."
350 sudo chown root:docker /var/run/docker.sock
351 sudo chmod 660 /var/run/docker.sock
352 else
353 warn "Docker socket not found. The service may have failed to start."
354 fi
355
356 log "Added $USER to the docker group and secured the socket."
357 fi
358}
359
360install_portainer() {
361 log "Installing Portainer CE..."
362
363 if ! require_cmd docker; then
364 err "Docker is required. Please install Docker (Option 9) first."
365 return 1
366 fi
367
368 if ! docker info >/dev/null 2>&1; then
369 err "Docker is installed but the daemon is not running. Start Docker, then retry Portainer."
370 return 1
371 fi
372
373 if docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
374 warn "Portainer container already exists."
375 if ! docker ps --format '{{.Names}}' | grep -Fxq portainer; then
376 log "Starting existing Portainer container..."
377 docker start portainer
378 fi
379 log "Portainer is available at https://localhost:9443"
380 return 0
381 fi
382
383 docker volume create portainer_data || {
384 err "Failed to create the Portainer data volume."
385 return 1
386 }
387
388 if ! docker run -d \
389 -p 8000:8000 \
390 -p 9443:9443 \
391 --name portainer \
392 --restart=always \
393 -v /var/run/docker.sock:/var/run/docker.sock \
394 -v portainer_data:/data \
395 portainer/portainer-ce:sts; then
396 err "Failed to start the Portainer container."
397 return 1
398 fi
399
400 log "Portainer is available at https://localhost:9443"
401}
402
403install_aws() {
404 if require_cmd aws; then warn "AWS CLI exists"; return; fi
405 log "Installing AWS CLI directly from Amazon..."
406
407 if [[ "$OS" == "macOS" ]]; then
408 curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg"
409 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
410 rm /tmp/AWSCLIV2.pkg
411 else
412 if ! require_cmd unzip; then
413 err "unzip is required. Please install 'Build Tools' (Option 1) first."
414 return 1
415 fi
416 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip"
417 cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install
418 rm -rf /tmp/awscliv2.zip /tmp/aws
419 fi
420}
421
422install_gcloud() {
423 local gcloud_dir="/opt/google-cloud-sdk"
424
425 if [[ -x "$gcloud_dir/bin/gcloud" ]]; then warn "Google Cloud CLI exists at $gcloud_dir"; return; fi
426 log "Installing Google Cloud CLI to $gcloud_dir..."
427 curl -fsSL https://sdk.cloud.google.com | sudo bash -s -- --disable-prompts --install-dir=/opt
428 sudo chown -R "$(id -u):$(id -g)" "$gcloud_dir"
429
430 if [[ "$CURRENT_SHELL" == "zsh" ]]; then
431 add_to_path_config "GCLOUD" '[ -f "/opt/google-cloud-sdk/path.zsh.inc" ] && source "/opt/google-cloud-sdk/path.zsh.inc"'
432 else
433 add_to_path_config "GCLOUD" '[ -f "/opt/google-cloud-sdk/path.bash.inc" ] && source "/opt/google-cloud-sdk/path.bash.inc"'
434 fi
435}
436
437install_firebase() {
438 if require_cmd firebase; then warn "Firebase CLI exists"; return; fi
439 log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..."
440
441 if ! require_cmd npm; then
442 err "NPM not found. Please install NVM (Option 3) first."
443 return 1
444 fi
445
446 npm install -g firebase-tools
447}
448
449# =======================================================
450# DEV TOOLS & SECURITY
451# =======================================================
452
453install_gh() {
454 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
455 log "Fetching latest GitHub CLI version..."
456 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
457 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
458
459 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz"
460 tar -xzf /tmp/gh.tar.gz -C /tmp
461 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
462 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
463}
464
465install_infisical() {
466 if require_cmd infisical; then warn "Infisical CLI exists"; return; fi
467 log "Installing Infisical CLI via NPM..."
468
469 if ! require_cmd npm; then
470 err "NPM not found. Please install NVM (Option 3) first."
471 return 1
472 fi
473
474 npm install -g @infisical/cli
475}
476
477# =======================================================
478# AI & AGENTIC TOOLS
479# =======================================================
480
481install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
482
483# opencode.ai/install now serves the v1 branch; OpenCode 2 has its own installer.
484# It installs to ~/.opencode/bin and is documented to run under bash.
485install_opencode() { log "Installing OpenCode 2..."; curl -fsSL https://opencode.ai/v2/install | bash; }
486
487install_codex() {
488 log "Installing @openai/codex..."
489 if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi
490 npm i -g @openai/codex
491}
492
493install_augment() {
494 if require_cmd auggie; then warn "Augment Code CLI exists"; return; fi
495 log "Installing Augment Code CLI (Auggie)..."
496 if ! require_cmd npm; then err "Node.js 20+/NPM is required. Install NVM (3) first."; return 1; fi
497 npm install -g @augmentcode/auggie
498}
499
500install_coderabbit() {
501 if require_cmd coderabbit || require_cmd cr; then warn "CodeRabbit CLI exists"; return; fi
502 log "Installing CodeRabbit CLI..."
503 curl -fsSL https://cli.coderabbit.ai/install.sh | sh
504}
505
506update_all() {
507 log "Updating all installed software from official sources..."
508
509 if require_cmd apt-get; then
510 sudo apt-get update && sudo apt-get install -y --only-upgrade \
511 build-essential curl wget git jq unzip libssl-dev zlib1g-dev libffi-dev libsqlite3-dev \
512 python3 python3-pip python3-venv
513 elif require_cmd dnf; then
514 sudo dnf upgrade -y python3 python3-pip
515 elif require_cmd zypper; then
516 sudo zypper --non-interactive update python3 python3-pip
517 elif require_cmd pacman; then
518 sudo pacman -Syu --noconfirm python python-pip
519 elif require_cmd apk; then
520 sudo apk upgrade
521 fi
522
523 if [[ "$OS" == "macOS" ]] && require_cmd python3; then
524 install_python
525 fi
526
527 require_cmd brew && { brew update && brew upgrade; }
528
529 if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
530 set +u
531 export NVM_DIR="$HOME/.nvm"
532 LATEST_NVM=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
533 curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${LATEST_NVM}/install.sh" | PROFILE=/dev/null $CURRENT_SHELL
534 \. "$NVM_DIR/nvm.sh"
535 nvm install --lts --reinstall-packages-from=current
536 nvm alias default 'lts/*'
537 nvm use --lts
538 set -u
539 fi
540
541 require_cmd go && install_go
542
543 if [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then
544 set +u
545 source "$HOME/.sdkman/bin/sdkman-init.sh"
546 sdk selfupdate force
547 sdk upgrade
548 set -u
549 fi
550
551 if require_cmd dotnet; then
552 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
553 fi
554
555 if require_cmd docker; then
556 if [[ "$OS" == "macOS" ]]; then
557 docker desktop update || warn "Update Docker Desktop from its application menu."
558 else
559 curl -fsSL https://get.docker.com | sudo sh
560 fi
561 fi
562
563 if require_cmd docker && docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
564 log "Updating Portainer CE..."
565 docker pull portainer/portainer-ce:sts && \
566 docker stop portainer && \
567 docker rm portainer && \
568 docker run -d \
569 -p 8000:8000 \
570 -p 9443:9443 \
571 --name portainer \
572 --restart=always \
573 -v /var/run/docker.sock:/var/run/docker.sock \
574 -v portainer_data:/data \
575 portainer/portainer-ce:sts
576 fi
577
578 if require_cmd aws; then
579 if [[ "$OS" == "macOS" ]]; then
580 curl -fsSL -o /tmp/AWSCLIV2.pkg https://awscli.amazonaws.com/AWSCLIV2.pkg && \
581 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
582 rm -f /tmp/AWSCLIV2.pkg
583 elif require_cmd unzip; then
584 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" && \
585 rm -rf /tmp/aws && unzip -q /tmp/awscliv2.zip -d /tmp && \
586 sudo /tmp/aws/install --update
587 rm -rf /tmp/aws /tmp/awscliv2.zip
588 fi
589 fi
590
591 require_cmd gcloud && gcloud components update --quiet
592 require_cmd firebase && npm install -g firebase-tools@latest
593
594 if require_cmd gh; then
595 log "Updating GitHub CLI..."
596 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
597 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
598 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz" && \
599 tar -xzf /tmp/gh.tar.gz -C /tmp && \
600 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
601 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
602 fi
603
604 require_cmd infisical && npm install -g @infisical/cli@latest
605 require_cmd claude && { curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
606 require_cmd codex && npm install -g @openai/codex@latest
607 require_cmd opencode && { curl -fsSL https://opencode.ai/v2/install | bash; }
608 require_cmd auggie && npm install -g @augmentcode/auggie@latest
609 if require_cmd coderabbit || require_cmd cr; then
610 curl -fsSL https://cli.coderabbit.ai/install.sh | sh
611 fi
612
613 log "Finished updating installed software. Review any warnings or errors above."
614}
615
616# =======================================================
617# MENU LOGIC
618# =======================================================
619show_menu() {
620 clear
621 printf "${BLUE}==================================================${NC}\n"
622 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
623 printf "${BLUE}==================================================${NC}\n"
624 printf "${YELLOW}--- Essentials & Package Managers ---${NC}\n"
625 printf " 1) Build Tools (GCC/Make/Git/Curl/Unzip)\n"
626 printf " 2) Homebrew (Optional)\n"
627 printf "${YELLOW}--- Languages & Runtimes ---${NC}\n"
628 printf " 3) NVM + Node.js\n"
629 printf " 4) Python 3\n"
630 printf " 5) Go\n"
631 printf " 6) SDKMAN (Java/Kotlin/Gradle)\n"
632 printf " 7) .NET\n"
633 printf "${YELLOW}--- Containers & Infrastructure ---${NC}\n"
634 printf " 9) Docker\n"
635 printf " 13) Portainer (Docker UI)\n"
636 printf "${YELLOW}--- Cloud & App Platforms ---${NC}\n"
637 printf " 10) AWS CLI\n"
638 printf " 11) Google Cloud CLI (gcloud)\n"
639 printf " 12) Firebase CLI\n"
640 printf "${YELLOW}--- Developer Productivity ---${NC}\n"
641 printf " 14) GitHub CLI\n"
642 printf "${YELLOW}--- Secrets & Security ---${NC}\n"
643 printf " 15) Infisical CLI\n"
644 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
645 printf " 16) Claude Code CLI\n"
646 printf " 17) OpenAI Codex CLI\n"
647 printf " 18) OpenCode\n"
648 printf " 20) Augment Code CLI (Auggie)\n"
649 printf " 21) CodeRabbit CLI\n"
650 printf "${BLUE}==================================================${NC}\n"
651 printf " 19) Update All Installed Software\n"
652 printf " 99) Quit & Refresh Shell\n"
653 printf "${BLUE}==================================================${NC}\n"
654}
655
656while true; do
657 show_menu
658
659 # Bulletproof prompt for both Bash and Zsh
660 printf "Select options (space-separated): "
661 if ! read input; then
662 printf "\n"
663 warn "Input closed. Exiting installer."
664 exit 0
665 fi
666
667 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
668 for choice in $input; do
669 case "$choice" in
670 1) install_build_tools ;;
671 2) install_brew ;;
672 3) install_nvm ;;
673 4) install_python ;;
674 5) install_go ;;
675 6) install_sdkman ;;
676 7) install_dotnet ;;
677 9) install_docker ;;
678 10) install_aws ;;
679 11) install_gcloud ;;
680 12) install_firebase ;;
681 13) install_portainer ;;
682 14) install_gh ;;
683 15) install_infisical ;;
684 16) install_claude ;;
685 17) install_codex ;;
686 18) install_opencode ;;
687 19) update_all ;;
688 20) install_augment ;;
689 21) install_coderabbit ;;
690 99)
691 log "Installation complete! Refreshing terminal environment..."
692
693 # If on Linux, forcefully inherit the new docker group without needing a logout
694 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
695 exec sg docker -c "exec ${SHELL:-zsh}"
696 else
697 exec "${SHELL:-zsh}"
698 fi
699 ;;
700 *) warn "Option $choice not valid." ;;
701 esac
702 done
703
704 printf "Press Enter to continue..."
705 read dummy || exit 0
706done