Utoljára aktív 6 days ago

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

Revízió 36264b8146361599792423c1024c595c3f8f99a9

README.md Eredeti

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 Eredeti
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# Prints the major Java version of the given java binary, or fails.
300# Handles both "21.0.4" and legacy "1.8.0_xxx" style version strings
301# (legacy ones report as 1, which correctly counts as too old).
302java_major() {
303 local out
304 out=$("$1" -version 2>&1) || return 1
305 printf '%s\n' "$out" | sed -nE 's/.*version "([0-9]+).*/\1/p' | head -1
306}
307
308# The Android command line tools (sdkmanager, avdmanager) are Java programs
309# and need JDK 17 or newer. On macOS, /usr/bin/java is a stub that exists even
310# when no JDK is installed, so `command -v java` is not enough: we must run it.
311ensure_android_java() {
312 local major home jbr
313
314 # Pick up an SDKMAN managed JDK if SDKMAN is installed.
315 if [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then
316 set +u; source "$HOME/.sdkman/bin/sdkman-init.sh"; set -u
317 fi
318
319 major=$(java_major java 2>/dev/null)
320 if [[ -n "$major" ]] && (( major >= 17 )); then
321 log "Using Java $major for the Android SDK tools."
322 return 0
323 fi
324
325 if [[ "$OS" == "macOS" ]]; then
326 # A JDK registered with macOS (Temurin, Zulu, Oracle .pkg installers).
327 home=$(/usr/libexec/java_home -v 17+ 2>/dev/null || true)
328 if [[ -n "$home" && -x "$home/bin/java" ]]; then
329 export JAVA_HOME="$home"
330 export PATH="$JAVA_HOME/bin:$PATH"
331 log "Using the JDK at $JAVA_HOME"
332 return 0
333 fi
334
335 # The JDK bundled inside Android Studio (not registered with macOS).
336 jbr="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
337 if [[ -x "$jbr/bin/java" ]]; then
338 add_to_path_config "ANDROID_STUDIO_JBR" 'export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
339export PATH="$JAVA_HOME/bin:$PATH"'
340 log "Using the JDK bundled with Android Studio."
341 return 0
342 fi
343 fi
344
345 # Nothing usable: provision a JDK through SDKMAN.
346 if [[ ! -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then
347 log "No Java 17+ found. Installing SDKMAN to provide a JDK..."
348 install_sdkman
349 fi
350
351 if [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then
352 log "Installing the default Temurin JDK through SDKMAN..."
353 set +u
354 source "$HOME/.sdkman/bin/sdkman-init.sh"
355 yes | sdk install java
356 # Re-source so the freshly installed JDK lands on PATH and JAVA_HOME.
357 source "$HOME/.sdkman/bin/sdkman-init.sh"
358 set -u
359
360 major=$(java_major java 2>/dev/null)
361 if [[ -n "$major" ]] && (( major >= 17 )); then
362 log "Using Java $major for the Android SDK tools."
363 return 0
364 fi
365 fi
366
367 err "Java 17 or newer is required by the Android SDK tools, but none could be set up."
368 err "Install a JDK (for example: sdk install java), open a new terminal, then rerun Option 8."
369 return 1
370}
371
372# Prints the download URL of the newest Android command line tools zip.
373# Tries the Android Studio download page first, then falls back to Google's
374# SDK repository manifest, which is a stable machine readable source.
375latest_cmdline_tools_url() {
376 local tools_os=$1 file
377
378 file=$(curl -fsSL --compressed https://developer.android.com/studio 2>/dev/null \
379 | grep -oE "commandlinetools-${tools_os}-[0-9]+_latest\.zip" \
380 | head -1)
381
382 if [[ -z "$file" ]]; then
383 file=$(curl -fsSL --compressed https://dl.google.com/android/repository/repository2-3.xml 2>/dev/null \
384 | grep -oE "commandlinetools-${tools_os}-[0-9]+_latest\.zip" \
385 | sort -t- -k3 -n \
386 | tail -1)
387 fi
388
389 [[ -n "$file" ]] && printf 'https://dl.google.com/android/repository/%s\n' "$file"
390}
391
392install_android() {
393 log "Installing Android SDK command-line tools..."
394
395 if ! require_cmd unzip; then
396 err "unzip is required. Please install 'Build Tools' (Option 1) first."
397 return 1
398 fi
399
400 if [[ "$OS_LOWER" == "linux" && "$SYS_ARCH" != "amd64" ]]; then
401 err "Google's Android command-line tools installer supports Linux x86_64 only."
402 return 1
403 fi
404
405 if [[ "$OS" == "macOS" ]]; then
406 TOOLS_OS="mac"
407 elif [[ "$OS_LOWER" == "linux" ]]; then
408 TOOLS_OS="linux"
409 else
410 err "Android SDK installation is not supported for $OS."
411 return 1
412 fi
413
414 # sdkmanager is a Java program, so fail fast before downloading anything.
415 ensure_android_java || return 1
416
417 ANDROID_HOME="$HOME/Android/Sdk"
418 ANDROID_SDK_ROOT="$ANDROID_HOME"
419 export ANDROID_HOME ANDROID_SDK_ROOT
420
421 mkdir -p "$ANDROID_HOME/cmdline-tools"
422
423 log "Finding latest Android command-line tools from Google..."
424 TOOLS_URL=$(latest_cmdline_tools_url "$TOOLS_OS")
425
426 if [[ -z "$TOOLS_URL" ]]; then
427 err "Could not find the Android command-line tools download URL."
428 return 1
429 fi
430
431 log "Downloading $TOOLS_URL..."
432 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
433 mkdir -p /tmp/android-cmdline-tools
434 if ! curl -fsSL -o /tmp/android-cmdline-tools.zip "$TOOLS_URL" || \
435 ! unzip -q /tmp/android-cmdline-tools.zip -d /tmp/android-cmdline-tools; then
436 err "Failed to download or extract the Android command-line tools."
437 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
438 return 1
439 fi
440
441 rm -rf "$ANDROID_HOME/cmdline-tools/latest"
442 mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
443 mv /tmp/android-cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
444 rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
445
446 SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
447 if [[ ! -x "$SDKMANAGER" ]]; then
448 err "sdkmanager was not installed correctly."
449 return 1
450 fi
451
452 # stdout is silenced, stderr is kept so Java or network errors stay visible.
453 log "Accepting Android SDK licenses..."
454 if ! yes | "$SDKMANAGER" --sdk_root="$ANDROID_HOME" --licenses >/dev/null; then
455 err "sdkmanager could not run. See the error above."
456 return 1
457 fi
458
459 log "Installing Android SDK packages..."
460 if ! "$SDKMANAGER" --sdk_root="$ANDROID_HOME" \
461 "cmdline-tools;latest" \
462 "platform-tools" \
463 "emulator" \
464 "platforms;android-36" \
465 "build-tools;36.0.0"; then
466 err "Failed to install Android SDK packages."
467 return 1
468 fi
469
470 add_to_path_config "ANDROID_SDK" 'export ANDROID_HOME="$HOME/Android/Sdk"
471export ANDROID_SDK_ROOT="$ANDROID_HOME"
472export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"'
473
474 log "Android SDK installation sequence finished!"
475}
476
477# =======================================================
478# CLOUD & INFRASTRUCTURE
479# =======================================================
480
481install_docker() {
482 log "Checking Docker status..."
483
484 # 1. Install if missing
485 if ! require_cmd docker; then
486 log "Installing Docker from official source..."
487 if [[ "$OS" == "macOS" ]]; then
488 [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64"
489 DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg"
490 curl -fsSL -o /tmp/Docker.dmg "$DMG_URL"
491 hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker
492 sudo cp -a /Volumes/Docker/Docker.app /Applications/
493 hdiutil detach /Volumes/Docker
494 rm /tmp/Docker.dmg
495
496 log "Starting Docker Desktop..."
497 open /Applications/Docker.app
498 log "Please complete the setup in the Docker Desktop UI."
499 else
500 curl -fsSL https://get.docker.com | sudo sh
501 fi
502 else
503 warn "Docker is already installed. Enforcing permissions..."
504 fi
505
506 # 2. Aggressively enforce Linux permissions
507 if [[ "$OS" != "macOS" ]]; then
508 log "Applying strict Linux post-install actions..."
509
510 # Ensure docker group exists and user is added
511 sudo groupadd -f docker
512 sudo usermod -aG docker "$USER"
513
514 # Ensure services are enabled and running
515 if command -v systemctl >/dev/null 2>&1; then
516 sudo systemctl enable --now docker.service
517 sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true
518 elif [[ "$OS" == "WSL" ]]; then
519 sudo service docker start
520 fi
521
522 # Give the system a second to generate the socket file
523 sleep 2
524
525 # Lock in ownership and permissions on the socket
526 if [[ -S /var/run/docker.sock ]]; then
527 log "Securing /var/run/docker.sock..."
528 sudo chown root:docker /var/run/docker.sock
529 sudo chmod 660 /var/run/docker.sock
530 else
531 warn "Docker socket not found. The service may have failed to start."
532 fi
533
534 log "Added $USER to the docker group and secured the socket."
535 fi
536}
537
538install_portainer() {
539 log "Installing Portainer CE..."
540
541 if ! require_cmd docker; then
542 err "Docker is required. Please install Docker (Option 9) first."
543 return 1
544 fi
545
546 if ! docker info >/dev/null 2>&1; then
547 err "Docker is installed but the daemon is not running. Start Docker, then retry Portainer."
548 return 1
549 fi
550
551 if docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
552 warn "Portainer container already exists."
553 if ! docker ps --format '{{.Names}}' | grep -Fxq portainer; then
554 log "Starting existing Portainer container..."
555 docker start portainer
556 fi
557 log "Portainer is available at https://localhost:9443"
558 return 0
559 fi
560
561 docker volume create portainer_data || {
562 err "Failed to create the Portainer data volume."
563 return 1
564 }
565
566 if ! docker run -d \
567 -p 8000:8000 \
568 -p 9443:9443 \
569 --name portainer \
570 --restart=always \
571 -v /var/run/docker.sock:/var/run/docker.sock \
572 -v portainer_data:/data \
573 portainer/portainer-ce:sts; then
574 err "Failed to start the Portainer container."
575 return 1
576 fi
577
578 log "Portainer is available at https://localhost:9443"
579}
580
581install_aws() {
582 if require_cmd aws; then warn "AWS CLI exists"; return; fi
583 log "Installing AWS CLI directly from Amazon..."
584
585 if [[ "$OS" == "macOS" ]]; then
586 curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg"
587 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
588 rm /tmp/AWSCLIV2.pkg
589 else
590 if ! require_cmd unzip; then
591 err "unzip is required. Please install 'Build Tools' (Option 1) first."
592 return 1
593 fi
594 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip"
595 cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install
596 rm -rf /tmp/awscliv2.zip /tmp/aws
597 fi
598}
599
600install_gcloud() {
601 local gcloud_dir="/opt/google-cloud-sdk"
602
603 if [[ -x "$gcloud_dir/bin/gcloud" ]]; then warn "Google Cloud CLI exists at $gcloud_dir"; return; fi
604 log "Installing Google Cloud CLI to $gcloud_dir..."
605 curl -fsSL https://sdk.cloud.google.com | sudo bash -s -- --disable-prompts --install-dir=/opt
606 sudo chown -R "$(id -u):$(id -g)" "$gcloud_dir"
607
608 if [[ "$CURRENT_SHELL" == "zsh" ]]; then
609 add_to_path_config "GCLOUD" '[ -f "/opt/google-cloud-sdk/path.zsh.inc" ] && source "/opt/google-cloud-sdk/path.zsh.inc"'
610 else
611 add_to_path_config "GCLOUD" '[ -f "/opt/google-cloud-sdk/path.bash.inc" ] && source "/opt/google-cloud-sdk/path.bash.inc"'
612 fi
613}
614
615install_firebase() {
616 if require_cmd firebase; then warn "Firebase CLI exists"; return; fi
617 log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..."
618
619 if ! require_cmd npm; then
620 err "NPM not found. Please install NVM (Option 3) first."
621 return 1
622 fi
623
624 npm install -g firebase-tools
625}
626
627# =======================================================
628# DEV TOOLS & SECURITY
629# =======================================================
630
631install_gh() {
632 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
633 log "Fetching latest GitHub CLI version..."
634 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
635 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
636
637 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz"
638 tar -xzf /tmp/gh.tar.gz -C /tmp
639 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
640 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
641}
642
643install_infisical() {
644 if require_cmd infisical; then warn "Infisical CLI exists"; return; fi
645 log "Installing Infisical CLI via NPM..."
646
647 if ! require_cmd npm; then
648 err "NPM not found. Please install NVM (Option 3) first."
649 return 1
650 fi
651
652 npm install -g @infisical/cli
653}
654
655# =======================================================
656# AI & AGENTIC TOOLS
657# =======================================================
658
659install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
660
661install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
662
663install_codex() {
664 log "Installing @openai/codex..."
665 if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi
666 npm i -g @openai/codex
667}
668
669install_augment() {
670 if require_cmd auggie; then warn "Augment Code CLI exists"; return; fi
671 log "Installing Augment Code CLI (Auggie)..."
672 if ! require_cmd npm; then err "Node.js 20+/NPM is required. Install NVM (3) first."; return 1; fi
673 npm install -g @augmentcode/auggie
674}
675
676install_coderabbit() {
677 if require_cmd coderabbit || require_cmd cr; then warn "CodeRabbit CLI exists"; return; fi
678 log "Installing CodeRabbit CLI..."
679 curl -fsSL https://cli.coderabbit.ai/install.sh | sh
680}
681
682update_all() {
683 log "Updating all installed software from official sources..."
684
685 if require_cmd apt-get; then
686 sudo apt-get update && sudo apt-get install -y --only-upgrade \
687 build-essential curl wget git jq unzip libssl-dev zlib1g-dev libffi-dev libsqlite3-dev \
688 python3 python3-pip python3-venv
689 elif require_cmd dnf; then
690 sudo dnf upgrade -y python3 python3-pip
691 elif require_cmd zypper; then
692 sudo zypper --non-interactive update python3 python3-pip
693 elif require_cmd pacman; then
694 sudo pacman -Syu --noconfirm python python-pip
695 elif require_cmd apk; then
696 sudo apk upgrade
697 fi
698
699 if [[ "$OS" == "macOS" ]] && require_cmd python3; then
700 install_python
701 fi
702
703 require_cmd brew && { brew update && brew upgrade; }
704
705 if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
706 set +u
707 export NVM_DIR="$HOME/.nvm"
708 LATEST_NVM=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
709 curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${LATEST_NVM}/install.sh" | PROFILE=/dev/null $CURRENT_SHELL
710 \. "$NVM_DIR/nvm.sh"
711 nvm install --lts --reinstall-packages-from=current
712 nvm alias default 'lts/*'
713 nvm use --lts
714 set -u
715 fi
716
717 require_cmd go && install_go
718
719 if [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then
720 set +u
721 source "$HOME/.sdkman/bin/sdkman-init.sh"
722 sdk selfupdate force
723 sdk upgrade
724 set -u
725 fi
726
727 if require_cmd dotnet; then
728 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
729 fi
730
731 local sdkmanager="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-$HOME/Android/Sdk}}/cmdline-tools/latest/bin/sdkmanager"
732 if [[ -x "$sdkmanager" ]]; then
733 if ensure_android_java; then
734 yes | "$sdkmanager" --licenses >/dev/null || true
735 "$sdkmanager" --update
736 else
737 warn "Skipping the Android SDK update because no usable Java was found."
738 fi
739 fi
740
741 if require_cmd docker; then
742 if [[ "$OS" == "macOS" ]]; then
743 docker desktop update || warn "Update Docker Desktop from its application menu."
744 else
745 curl -fsSL https://get.docker.com | sudo sh
746 fi
747 fi
748
749 if require_cmd docker && docker ps -a --format '{{.Names}}' | grep -Fxq portainer; then
750 log "Updating Portainer CE..."
751 docker pull portainer/portainer-ce:sts && \
752 docker stop portainer && \
753 docker rm portainer && \
754 docker run -d \
755 -p 8000:8000 \
756 -p 9443:9443 \
757 --name portainer \
758 --restart=always \
759 -v /var/run/docker.sock:/var/run/docker.sock \
760 -v portainer_data:/data \
761 portainer/portainer-ce:sts
762 fi
763
764 if require_cmd aws; then
765 if [[ "$OS" == "macOS" ]]; then
766 curl -fsSL -o /tmp/AWSCLIV2.pkg https://awscli.amazonaws.com/AWSCLIV2.pkg && \
767 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
768 rm -f /tmp/AWSCLIV2.pkg
769 elif require_cmd unzip; then
770 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" && \
771 rm -rf /tmp/aws && unzip -q /tmp/awscliv2.zip -d /tmp && \
772 sudo /tmp/aws/install --update
773 rm -rf /tmp/aws /tmp/awscliv2.zip
774 fi
775 fi
776
777 require_cmd gcloud && gcloud components update --quiet
778 require_cmd firebase && npm install -g firebase-tools@latest
779
780 if require_cmd gh; then
781 log "Updating GitHub CLI..."
782 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
783 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
784 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz" && \
785 tar -xzf /tmp/gh.tar.gz -C /tmp && \
786 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
787 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
788 fi
789
790 require_cmd infisical && npm install -g @infisical/cli@latest
791 require_cmd claude && { curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
792 require_cmd codex && npm install -g @openai/codex@latest
793 require_cmd opencode && { curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
794 require_cmd auggie && npm install -g @augmentcode/auggie@latest
795 if require_cmd coderabbit || require_cmd cr; then
796 curl -fsSL https://cli.coderabbit.ai/install.sh | sh
797 fi
798
799 log "Finished updating installed software. Review any warnings or errors above."
800}
801
802# =======================================================
803# MENU LOGIC
804# =======================================================
805show_menu() {
806 clear
807 printf "${BLUE}==================================================${NC}\n"
808 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
809 printf "${BLUE}==================================================${NC}\n"
810 printf "${YELLOW}--- Essentials & Package Managers ---${NC}\n"
811 printf " 1) Build Tools (GCC/Make/Git/Curl/Unzip)\n"
812 printf " 2) Homebrew (Optional)\n"
813 printf "${YELLOW}--- Languages & Runtimes ---${NC}\n"
814 printf " 3) NVM + Node.js\n"
815 printf " 4) Python 3\n"
816 printf " 5) Go\n"
817 printf " 6) SDKMAN (Java/Kotlin/Gradle)\n"
818 printf " 7) .NET\n"
819 printf "${YELLOW}--- Mobile Development ---${NC}\n"
820 printf " 8) Android SDK (CLI + Platform Tools)\n"
821 printf "${YELLOW}--- Containers & Infrastructure ---${NC}\n"
822 printf " 9) Docker\n"
823 printf " 13) Portainer (Docker UI)\n"
824 printf "${YELLOW}--- Cloud & App Platforms ---${NC}\n"
825 printf " 10) AWS CLI\n"
826 printf " 11) Google Cloud CLI (gcloud)\n"
827 printf " 12) Firebase CLI\n"
828 printf "${YELLOW}--- Developer Productivity ---${NC}\n"
829 printf " 14) GitHub CLI\n"
830 printf "${YELLOW}--- Secrets & Security ---${NC}\n"
831 printf " 15) Infisical CLI\n"
832 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
833 printf " 16) Claude Code CLI\n"
834 printf " 17) OpenAI Codex CLI\n"
835 printf " 18) OpenCode\n"
836 printf " 20) Augment Code CLI (Auggie)\n"
837 printf " 21) CodeRabbit CLI\n"
838 printf "${BLUE}==================================================${NC}\n"
839 printf " 19) Update All Installed Software\n"
840 printf " 99) Quit & Refresh Shell\n"
841 printf "${BLUE}==================================================${NC}\n"
842}
843
844while true; do
845 show_menu
846
847 # Bulletproof prompt for both Bash and Zsh
848 printf "Select options (space-separated): "
849 if ! read input; then
850 printf "\n"
851 warn "Input closed. Exiting installer."
852 exit 0
853 fi
854
855 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
856 for choice in $input; do
857 case "$choice" in
858 1) install_build_tools ;;
859 2) install_brew ;;
860 3) install_nvm ;;
861 4) install_python ;;
862 5) install_go ;;
863 6) install_sdkman ;;
864 7) install_dotnet ;;
865 8) install_android ;;
866 9) install_docker ;;
867 10) install_aws ;;
868 11) install_gcloud ;;
869 12) install_firebase ;;
870 13) install_portainer ;;
871 14) install_gh ;;
872 15) install_infisical ;;
873 16) install_claude ;;
874 17) install_codex ;;
875 18) install_opencode ;;
876 19) update_all ;;
877 20) install_augment ;;
878 21) install_coderabbit ;;
879 99)
880 log "Installation complete! Refreshing terminal environment..."
881
882 # If on Linux, forcefully inherit the new docker group without needing a logout
883 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
884 exec sg docker -c "exec ${SHELL:-zsh}"
885 else
886 exec "${SHELL:-zsh}"
887 fi
888 ;;
889 *) warn "Option $choice not valid." ;;
890 esac
891 done
892
893 printf "Press Enter to continue..."
894 read dummy || exit 0
895done