Última actividad 3 weeks ago

Interactive developer environment bootstrapper for Zsh/Bash with repeatable workstation setup helpers.

Revisión 9ea7725418e446f7d6576cfec89d26d37ed2ae2a

README.md Sin formato

Strict Developer Environment Installer

A robust, idempotent, and interactive bash script to bootstrap your macOS, Linux, or WSL development environment.

Unlike standard setup scripts that rely heavily on package managers like Homebrew or Apt (which can introduce bloat, dependency hell, or outdated packages), this script is designed with a "Strict Official Source" philosophy. It dynamically fetches the absolute latest binaries and pre-compiled releases directly from the official maintainers (e.g., GitHub API, Python.org, Amazon).

✨ Key Features

  • Zero-Brew Dependency: By default, it bypasses Homebrew entirely, fetching tools directly from official APIs and release pages (Homebrew is available as an optional install).
  • Idempotent: Safe to run multiple times. It intelligently checks if a tool is already installed before attempting to download it.
  • Dynamic Versioning: Pings official APIs (like GitHub Releases or go.dev/VERSION) to ensure you are downloading the exact latest version at the time of execution.
  • Auto-Path Management: Intelligently injects paths into a dedicated ~/.pathrc (or your .bashrc/.zshrc) without mangling your config files.
  • Architecture Aware: Automatically detects if you are running on Intel (x86_64) or Apple Silicon/ARM (aarch64/arm64) and downloads the correct binaries.

🚀 Usage

You can launch the interactive installer directly from your terminal with a single command:

bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/af1c64c143a44ffbb0a1632dd6a32af1/raw/HEAD/menu.sh)"

Once the menu loads, simply follow the interactive prompts. You can select multiple tools at once by entering space-separated numbers (e.g., 1 3 8 12).

🛠️ Included Tools

Core Runtimes & Managers

  • Build Tools: GCC, Make, Git, Curl, Unzip, and core libraries.
  • Homebrew: (Optional) Installed officially.
  • NVM (Node Version Manager): Includes interactive prompt to install LTS or Latest Node.js immediately.
  • Python 3: Fetches the latest stable .pkg for Mac or compiles strictly from source for Linux.
  • Go: Fetches the latest tarball and installs system-wide to /usr/local/go.
  • SDKMAN: For managing Java, Kotlin, and Gradle.
  • .NET: Installed via Microsoft's official script.

Cloud & Infrastructure

  • Docker: Downloads the official .dmg (macOS) or runs the official get.docker.com script (Linux) with auto-group assignment.
  • AWS CLI: Fetches the official binaries directly from Amazon.
  • Google Cloud CLI (gcloud): Official Google setup script.
  • Firebase CLI: Standalone binary (no Node.js required natively).

Dev Tools & Security

  • GitHub CLI (gh): Downloaded directly from GitHub Releases to /usr/local/bin.
  • Infisical CLI: Downloaded directly from GitHub Releases for secret management.

AI & Agentic Tools

  • Claude Code CLI: Anthropic's official agent.
  • OpenCode: AI agent orchestration.
  • OpenAI Codex CLI: Required NVM/Node.js to run.

⚠️ Important Notes & Troubleshooting

  • Restart Your Shell: After running the script for the first time, you should restart your terminal or run source ~/.bashrc (or ~/.zshrc) to ensure all new PATH variables (like NVM, Go, or .NET) take effect.
  • Docker Permissions (Linux): The script automatically adds your user to the docker group. You will need to log out and log back in for this to take effect.
  • AWS CLI on Linux: Ensure you install Build Tools (Option 1) first if you are on a fresh Linux install, as the AWS installer requires unzip to extract the payload.

Built for developers who want total control over their toolchain.

menu.sh Sin formato
1#!/usr/bin/env bash
2
3# =======================================================
4# 1. BOOTSTRAPPER: PREFER ZSH, FALLBACK TO BASH
5# =======================================================
6if [ -z "${_PREFER_ZSH_BOOTSTRAPPED:-}" ]; then
7 export _PREFER_ZSH_BOOTSTRAPPED=1
8
9 # Only attempt to re-execute if $0 is an actual file on disk.
10 # This prevents the 'can't open input file' error when running
11 # from memory via `bash -c "$(curl ...)"` or `zsh -c`.
12 if [ -f "$0" ]; then
13 if command -v zsh >/dev/null 2>&1; then
14 exec zsh "$0" "$@"
15 elif command -v bash >/dev/null 2>&1; then
16 exec bash "$0" "$@"
17 else
18 echo "Error: Neither Zsh nor Bash is installed. Exiting."
19 exit 1
20 fi
21 fi
22fi
23
24set -u
25
26# =======================================================
27# 2. CROSS-SHELL NORMALIZATION
28# =======================================================
29if [ -n "${ZSH_VERSION:-}" ]; then
30 # Zsh: Enable Bash-like word splitting for unquoted variables (menu input)
31 setopt shwordsplit 2>/dev/null || true
32 CURRENT_SHELL="zsh"
33 PROFILE_FILE="$HOME/.zshrc"
34elif [ -n "${BASH_VERSION:-}" ]; then
35 # Bash
36 CURRENT_SHELL="bash"
37 PROFILE_FILE="$HOME/.bashrc"
38else
39 # Fallback POSIX
40 CURRENT_SHELL="sh"
41 PROFILE_FILE="$HOME/.profile"
42fi
43
44# =======================================================
45# COLORS & LOGGING
46# =======================================================
47GREEN="\033[0;32m"
48RED="\033[0;31m"
49YELLOW="\033[1;33m"
50BLUE="\033[0;34m"
51NC="\033[0m"
52
53log() { printf "${GREEN}▶ %s${NC}\n" "$*"; }
54warn() { printf "${YELLOW}⚠ %s${NC}\n" "$*"; }
55err() { printf "${RED}✖ %s${NC}\n" "$*"; }
56info() { printf "${BLUE}ℹ %s${NC}\n" "$*"; }
57
58# =======================================================
59# HELPERS & SYSTEM DETECTION
60# =======================================================
61require_cmd() { command -v "$1" >/dev/null 2>&1; }
62
63add_to_path_config() {
64 local label=$1
65 local path_line=$2
66 local target_file="${3:-$PROFILE_FILE}"
67
68 if [[ -f "$target_file" ]]; then
69 if ! grep -q "$label" "$target_file"; then
70 printf "\n# %s\n%s\n" "$label" "$path_line" >> "$target_file"
71 log "Added $label to $target_file"
72 fi
73 else
74 printf "\n# %s\n%s\n" "$label" "$path_line" >> "$PROFILE_FILE"
75 log "Added $label to shell profile ($PROFILE_FILE)."
76 fi
77
78 # Instantly evaluate the path line so the current script session
79 # can immediately use the newly installed tool in subsequent steps.
80 eval "$path_line" 2>/dev/null || true
81}
82
83detect_os_arch() {
84 ARCH=$(uname -m)
85 case "$ARCH" in
86 x86_64|amd64) SYS_ARCH="amd64"; MAC_ARCH="x86_64"; AWS_ARCH="x86_64" ;;
87 aarch64|arm64) SYS_ARCH="arm64"; MAC_ARCH="arm64"; AWS_ARCH="aarch64" ;;
88 *) err "Unsupported architecture: $ARCH"; exit 1 ;;
89 esac
90
91 if [[ "$OSTYPE" == "darwin"* ]]; then
92 OS="macOS"
93 OS_LOWER="darwin"
94 elif grep -qi microsoft /proc/version 2>/dev/null; then
95 OS="WSL"
96 OS_LOWER="linux"
97 elif [[ -f /etc/os-release ]]; then
98 . /etc/os-release
99 [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* ]] && OS="Ubuntu" || OS="Linux"
100 OS_LOWER="linux"
101 else
102 OS="Unknown"
103 OS_LOWER="unknown"
104 fi
105 log "Detected: $OS ($ARCH) running $CURRENT_SHELL"
106}
107
108detect_os_arch
109
110# =======================================================
111# CORE RUNTIMES & MANAGERS
112# =======================================================
113
114install_build_tools() {
115 log "Installing Build Essentials & Core Dependencies..."
116 case "$OS" in
117 Ubuntu|WSL) sudo apt-get update && sudo apt-get install -y build-essential curl wget git jq unzip libssl-dev zlib1g-dev libffi-dev libsqlite3-dev ;;
118 macOS) xcode-select --install || warn "Xcode tools already installed" ;;
119 *) warn "Manual installation required for $OS." ;;
120 esac
121}
122
123install_brew() {
124 if require_cmd brew; then warn "Homebrew already installed"; return; fi
125 log "Installing Homebrew from Official Source..."
126
127 # Homebrew's installer specifically requires Bash execution, regardless of current shell
128 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
129
130 if [[ "$OS_LOWER" == "linux" ]]; then
131 add_to_path_config "HOMEBREW" 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"'
132 elif [[ "$MAC_ARCH" == "arm64" ]]; then
133 add_to_path_config "HOMEBREW" 'eval "$(/opt/homebrew/bin/brew shellenv)"'
134 else
135 add_to_path_config "HOMEBREW" 'eval "$(/usr/local/bin/brew shellenv)"'
136 fi
137}
138
139install_nvm() {
140 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
184install_python() {
185 log "Fetching latest stable Python version..."
186
187 LATEST_PY=$(curl -s https://www.python.org/downloads/ | grep -o 'Download Python 3\.[0-9]\{1,2\}\.[0-9]\{1,2\}' | head -1 | grep -o '3\.[0-9]\{1,2\}\.[0-9]\{1,2\}')
188 [[ -z "$LATEST_PY" ]] && LATEST_PY="3.12.3"
189
190 log "Detected Stable Version: $LATEST_PY"
191
192 if [[ "$OS" == "macOS" ]]; then
193 log "Downloading official macOS package..."
194 PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg"
195 curl -fsSL -o /tmp/python.pkg "$PKG_URL"
196
197 log "Running installer..."
198 sudo installer -pkg /tmp/python.pkg -target /
199 rm /tmp/python.pkg
200 else
201 log "Installing build dependencies (zlib, ssl, etc)..."
202 sudo apt-get update && sudo apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev libsqlite3-dev
203
204 SRC_URL="https://www.python.org/ftp/python/${LATEST_PY}/Python-${LATEST_PY}.tgz"
205
206 log "Downloading $SRC_URL..."
207 curl -fSL -o /tmp/Python.tgz "$SRC_URL" || err "Failed to download Python source from $SRC_URL"
208
209 cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}"
210
211 log "Configuring build (with ensurepip)..."
212 ./configure --enable-optimizations --with-ensurepip=install
213
214 log "Compiling Python (this may take a few minutes)..."
215 sudo make altinstall
216
217 PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2)
218
219 log "Setting up symlinks..."
220 sudo ln -sf /usr/local/bin/python${PY_MINOR} /usr/local/bin/python3
221
222 if [[ -f "/usr/local/bin/pip${PY_MINOR}" ]]; then
223 sudo ln -sf /usr/local/bin/pip${PY_MINOR} /usr/local/bin/pip3
224 log "Successfully linked pip3!"
225 else
226 err "pip${PY_MINOR} was not generated during the build!"
227 fi
228
229 cd ~ && sudo rm -rf /tmp/Python*
230 fi
231
232 log "Python $LATEST_PY installation sequence finished!"
233}
234
235install_go() {
236 log "Fetching latest Go version from official source..."
237 LATEST_GO=$(curl -sL https://go.dev/VERSION?m=text | head -n 1)
238 TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz"
239
240 log "Downloading $TAR_FILE..."
241 curl -fsSL -o /tmp/go.tar.gz "https://go.dev/dl/$TAR_FILE"
242
243 log "Installing to /usr/local/go..."
244 sudo rm -rf /usr/local/go
245 sudo tar -C /usr/local -xzf /tmp/go.tar.gz
246 rm /tmp/go.tar.gz
247 add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"'
248}
249
250install_sdkman() {
251 if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi
252
253 # SDKMAN explicitly blocks macOS's default Bash 3.2.
254 # If we are trapped in Bash < 4, force the installer to use Zsh.
255 if [[ "$CURRENT_SHELL" == "bash" && "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then
256 log "Outdated Bash detected. Installing SDKMAN via Zsh..."
257 curl -s "https://get.sdkman.io" | zsh
258 else
259 log "Installing SDKMAN via $CURRENT_SHELL..."
260 curl -s "https://get.sdkman.io" | $CURRENT_SHELL
261 fi
262}
263
264install_dotnet() {
265 log "Installing .NET from official script..."
266 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
267 add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"'
268}
269
270# =======================================================
271# CLOUD & INFRASTRUCTURE
272# =======================================================
273
274install_docker() {
275 log "Checking Docker status..."
276
277 # 1. Install if missing
278 if ! require_cmd docker; then
279 log "Installing Docker from official source..."
280 if [[ "$OS" == "macOS" ]]; then
281 [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64"
282 DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg"
283 curl -fsSL -o /tmp/Docker.dmg "$DMG_URL"
284 hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker
285 sudo cp -a /Volumes/Docker/Docker.app /Applications/
286 hdiutil detach /Volumes/Docker
287 rm /tmp/Docker.dmg
288
289 log "Starting Docker Desktop..."
290 open /Applications/Docker.app
291 log "Please complete the setup in the Docker Desktop UI."
292 else
293 curl -fsSL https://get.docker.com | sudo sh
294 fi
295 else
296 warn "Docker is already installed. Enforcing permissions..."
297 fi
298
299 # 2. Aggressively enforce Linux permissions
300 if [[ "$OS" != "macOS" ]]; then
301 log "Applying strict Linux post-install actions..."
302
303 # Ensure docker group exists and user is added
304 sudo groupadd -f docker
305 sudo usermod -aG docker "$USER"
306
307 # Ensure services are enabled and running
308 if command -v systemctl >/dev/null 2>&1; then
309 sudo systemctl enable --now docker.service
310 sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true
311 elif [[ "$OS" == "WSL" ]]; then
312 sudo service docker start
313 fi
314
315 # Give the system a second to generate the socket file
316 sleep 2
317
318 # Lock in ownership and permissions on the socket
319 if [[ -S /var/run/docker.sock ]]; then
320 log "Securing /var/run/docker.sock..."
321 sudo chown root:docker /var/run/docker.sock
322 sudo chmod 660 /var/run/docker.sock
323 else
324 warn "Docker socket not found. The service may have failed to start."
325 fi
326
327 log "Added $USER to the docker group and secured the socket."
328 fi
329}
330
331install_aws() {
332 if require_cmd aws; then warn "AWS CLI exists"; return; fi
333 log "Installing AWS CLI directly from Amazon..."
334
335 if [[ "$OS" == "macOS" ]]; then
336 curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg"
337 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
338 rm /tmp/AWSCLIV2.pkg
339 else
340 if ! require_cmd unzip; then
341 err "unzip is required. Please install 'Build Tools' (Option 1) first."
342 return 1
343 fi
344 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip"
345 cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install
346 rm -rf /tmp/awscliv2.zip /tmp/aws
347 fi
348}
349
350install_gcloud() {
351 if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi
352 log "Installing Google Cloud CLI..."
353 curl -fsSL https://sdk.cloud.google.com | $CURRENT_SHELL -s -- --disable-prompts
354
355 if [[ "$CURRENT_SHELL" == "zsh" ]]; then
356 add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc"'
357 else
358 add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && source "$HOME/google-cloud-sdk/path.bash.inc"'
359 fi
360}
361
362install_firebase() {
363 if require_cmd firebase; then warn "Firebase CLI exists"; return; fi
364 log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..."
365
366 if ! require_cmd npm; then
367 err "NPM not found. Please install NVM (Option 3) first."
368 return 1
369 fi
370
371 npm install -g firebase-tools
372}
373
374# =======================================================
375# DEV TOOLS & SECURITY
376# =======================================================
377
378install_gh() {
379 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
380 log "Fetching latest GitHub CLI version..."
381 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
382 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
383
384 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz"
385 tar -xzf /tmp/gh.tar.gz -C /tmp
386 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
387 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
388}
389
390install_infisical() {
391 if require_cmd infisical; then warn "Infisical CLI exists"; return; fi
392 log "Installing Infisical CLI via NPM..."
393
394 if ! require_cmd npm; then
395 err "NPM not found. Please install NVM (Option 3) first."
396 return 1
397 fi
398
399 npm install -g @infisical/cli
400}
401
402# =======================================================
403# AI & AGENTIC TOOLS
404# =======================================================
405
406install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
407install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
408
409install_codex() {
410 log "Installing @openai/codex..."
411 if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi
412 npm i -g @openai/codex
413}
414
415# =======================================================
416# MENU LOGIC
417# =======================================================
418show_menu() {
419 clear
420 printf "${BLUE}==================================================${NC}\n"
421 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
422 printf "${BLUE}==================================================${NC}\n"
423 printf "${YELLOW}--- Core Runtimes & Managers ---${NC}\n"
424 printf " 1) Build Tools (GCC/Make)\n"
425 printf " 2) Homebrew (Optional)\n"
426 printf " 3) NVM (Node.js)\n"
427 printf " 4) Python 3 (Official API)\n"
428 printf " 5) Go (Official -> /usr/local)\n"
429 printf " 6) SDKMAN (Java/Kotlin)\n"
430 printf " 7) .NET (Official Script)\n"
431 printf "${YELLOW}--- Cloud & Infrastructure ---${NC}\n"
432 printf " 8) Docker (Official DMG/sh)\n"
433 printf " 9) AWS CLI (Official Bin)\n"
434 printf " 10) Google Cloud CLI (gcloud)\n"
435 printf " 11) Firebase CLI (NPM/ARM64 Safe)\n"
436 printf "${YELLOW}--- Dev Tools & Security ---${NC}\n"
437 printf " 12) GitHub CLI (Official Bin)\n"
438 printf " 13) Infisical (Official Bin)\n"
439 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
440 printf " 14) Claude Code CLI\n"
441 printf " 15) OpenCode (opencode.ai)\n"
442 printf " 16) OpenAI Codex CLI\n"
443 printf "${BLUE}==================================================${NC}\n"
444 printf " 99) Quit & Refresh Shell\n"
445 printf "${BLUE}==================================================${NC}\n"
446}
447
448while true; do
449 show_menu
450
451 # Bulletproof prompt for both Bash and Zsh
452 printf "Select options (space-separated): "
453 read input
454
455 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
456 for choice in $input; do
457 case "$choice" in
458 1) install_build_tools ;;
459 2) install_brew ;;
460 3) install_nvm ;;
461 4) install_python ;;
462 5) install_go ;;
463 6) install_sdkman ;;
464 7) install_dotnet ;;
465 8) install_docker ;;
466 9) install_aws ;;
467 10) install_gcloud ;;
468 11) install_firebase ;;
469 12) install_gh ;;
470 13) install_infisical ;;
471 14) install_claude ;;
472 15) install_opencode ;;
473 16) install_codex ;;
474 99)
475 log "Installation complete! Refreshing terminal environment..."
476
477 # If on Linux, forcefully inherit the new docker group without needing a logout
478 if [[ "$OS" != "macOS" ]] && command -v sg >/dev/null 2>&1; then
479 exec sg docker -c "exec ${SHELL:-zsh}"
480 else
481 exec "${SHELL:-zsh}"
482 fi
483 ;;
484 *) warn "Option $choice not valid." ;;
485 esac
486 done
487
488 printf "Press Enter to continue..."
489 read dummy
490done