最後活躍 3 weeks ago

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

修訂 05723d633b2891f503e6282b246fc2e943670992

README.md 原始檔案

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 原始檔案
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
79detect_os_arch() {
80 ARCH=$(uname -m)
81 case "$ARCH" in
82 x86_64|amd64) SYS_ARCH="amd64"; MAC_ARCH="x86_64"; AWS_ARCH="x86_64" ;;
83 aarch64|arm64) SYS_ARCH="arm64"; MAC_ARCH="arm64"; AWS_ARCH="aarch64" ;;
84 *) err "Unsupported architecture: $ARCH"; exit 1 ;;
85 esac
86
87 if [[ "$OSTYPE" == "darwin"* ]]; then
88 OS="macOS"
89 OS_LOWER="darwin"
90 elif grep -qi microsoft /proc/version 2>/dev/null; then
91 OS="WSL"
92 OS_LOWER="linux"
93 elif [[ -f /etc/os-release ]]; then
94 . /etc/os-release
95 [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* ]] && OS="Ubuntu" || OS="Linux"
96 OS_LOWER="linux"
97 else
98 OS="Unknown"
99 OS_LOWER="unknown"
100 fi
101 log "Detected: $OS ($ARCH) running $CURRENT_SHELL"
102}
103
104detect_os_arch
105
106# =======================================================
107# CORE RUNTIMES & MANAGERS
108# =======================================================
109
110install_build_tools() {
111 log "Installing Build Essentials & Core Dependencies..."
112 case "$OS" in
113 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 ;;
114 macOS) xcode-select --install || warn "Xcode tools already installed" ;;
115 *) warn "Manual installation required for $OS." ;;
116 esac
117}
118
119install_brew() {
120 if require_cmd brew; then warn "Homebrew already installed"; return; fi
121 log "Installing Homebrew from Official Source..."
122
123 # Homebrew's installer specifically requires Bash execution, regardless of current shell
124 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
125
126 if [[ "$OS_LOWER" == "linux" ]]; then
127 add_to_path_config "HOMEBREW" 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"'
128 elif [[ "$MAC_ARCH" == "arm64" ]]; then
129 add_to_path_config "HOMEBREW" 'eval "$(/opt/homebrew/bin/brew shellenv)"'
130 else
131 add_to_path_config "HOMEBREW" 'eval "$(/usr/local/bin/brew shellenv)"'
132 fi
133}
134
135install_nvm() {
136 export NVM_DIR="$HOME/.nvm"
137
138 if [ -d "$NVM_DIR" ]; then
139 warn "NVM is already installed."
140 else
141 log "Installing NVM via $CURRENT_SHELL..."
142 curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | $CURRENT_SHELL
143 fi
144
145 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
146
147 printf "\n${YELLOW}Which version of Node.js would you like to install?${NC}\n"
148 printf " 1) LTS (Long Term Support - Recommended for stability)\n"
149 printf " 2) Latest (Current features - Recommended for testing new APIs)\n"
150 printf " 3) Skip installing Node.js right now\n"
151
152 printf "Select (1/2/3): "
153 read node_choice
154
155 case "$node_choice" in
156 2) log "Installing Latest Node.js..."; nvm install node; nvm alias default node; nvm use node ;;
157 3) log "Skipping Node.js installation." ;;
158 *) log "Installing Latest LTS Node.js..."; nvm install --lts; nvm alias default 'lts/*'; nvm use --lts ;;
159 esac
160}
161
162install_python() {
163 log "Fetching latest stable Python version..."
164
165 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\}')
166 [[ -z "$LATEST_PY" ]] && LATEST_PY="3.12.3"
167
168 log "Detected Stable Version: $LATEST_PY"
169
170 if [[ "$OS" == "macOS" ]]; then
171 log "Downloading official macOS package..."
172 PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg"
173 curl -fsSL -o /tmp/python.pkg "$PKG_URL"
174
175 log "Running installer..."
176 sudo installer -pkg /tmp/python.pkg -target /
177 rm /tmp/python.pkg
178 else
179 log "Installing build dependencies (zlib, ssl, etc)..."
180 sudo apt-get update && sudo apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev libsqlite3-dev
181
182 SRC_URL="https://www.python.org/ftp/python/${LATEST_PY}/Python-${LATEST_PY}.tgz"
183
184 log "Downloading $SRC_URL..."
185 curl -fSL -o /tmp/Python.tgz "$SRC_URL" || err "Failed to download Python source from $SRC_URL"
186
187 cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}"
188
189 log "Configuring build (with ensurepip)..."
190 ./configure --enable-optimizations --with-ensurepip=install
191
192 log "Compiling Python (this may take a few minutes)..."
193 sudo make altinstall
194
195 PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2)
196
197 log "Setting up symlinks..."
198 sudo ln -sf /usr/local/bin/python${PY_MINOR} /usr/local/bin/python3
199
200 if [[ -f "/usr/local/bin/pip${PY_MINOR}" ]]; then
201 sudo ln -sf /usr/local/bin/pip${PY_MINOR} /usr/local/bin/pip3
202 log "Successfully linked pip3!"
203 else
204 err "pip${PY_MINOR} was not generated during the build!"
205 fi
206
207 cd ~ && sudo rm -rf /tmp/Python*
208 fi
209
210 log "Python $LATEST_PY installation sequence finished!"
211}
212
213install_go() {
214 log "Fetching latest Go version from official source..."
215 LATEST_GO=$(curl -sL https://go.dev/VERSION?m=text | head -n 1)
216 TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz"
217
218 log "Downloading $TAR_FILE..."
219 curl -fsSL -o /tmp/go.tar.gz "https://go.dev/dl/$TAR_FILE"
220
221 log "Installing to /usr/local/go..."
222 sudo rm -rf /usr/local/go
223 sudo tar -C /usr/local -xzf /tmp/go.tar.gz
224 rm /tmp/go.tar.gz
225 add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"'
226}
227
228install_sdkman() {
229 if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi
230
231 # SDKMAN explicitly blocks macOS's default Bash 3.2.
232 # If we are trapped in Bash < 4, force the installer to use Zsh.
233 if [[ "$CURRENT_SHELL" == "bash" && "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then
234 log "Outdated Bash detected. Installing SDKMAN via Zsh..."
235 curl -s "https://get.sdkman.io" | zsh
236 else
237 log "Installing SDKMAN via $CURRENT_SHELL..."
238 curl -s "https://get.sdkman.io" | $CURRENT_SHELL
239 fi
240}
241
242install_dotnet() {
243 log "Installing .NET from official script..."
244 curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
245 add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"'
246}
247
248# =======================================================
249# CLOUD & INFRASTRUCTURE
250# =======================================================
251
252install_docker() {
253 if require_cmd docker; then log "Docker exists"; else
254 log "Installing Docker from official source..."
255
256 if [[ "$OS" == "macOS" ]]; then
257 [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64"
258 DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg"
259 curl -fsSL -o /tmp/Docker.dmg "$DMG_URL"
260 hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker
261 sudo cp -a /Volumes/Docker/Docker.app /Applications/
262 hdiutil detach /Volumes/Docker
263 rm /tmp/Docker.dmg
264
265 log "Starting Docker Desktop..."
266 open /Applications/Docker.app
267 log "Please complete the setup in the Docker Desktop UI."
268 else
269 curl -fsSL https://get.docker.com | sudo sh
270 fi
271 fi
272
273 if [[ "$OS" != "macOS" ]]; then
274 log "Applying Linux post-install actions..."
275
276 sudo groupadd -f docker
277 sudo usermod -aG docker "$USER"
278
279 if command -v systemctl >/dev/null 2>&1; then
280 sudo systemctl enable --now docker.service
281 sudo systemctl enable --now containerd.service
282 elif [[ "$OS" == "WSL" ]]; then
283 sudo service docker start
284 fi
285
286 if [[ -S /var/run/docker.sock ]]; then
287 log "Fixing ownership of /var/run/docker.sock..."
288 sudo chown root:docker /var/run/docker.sock
289 sudo chmod 660 /var/run/docker.sock
290 fi
291
292 log "Added $USER to the docker group."
293 log "CRITICAL: To apply group changes immediately, run: newgrp docker"
294 fi
295}
296
297install_aws() {
298 if require_cmd aws; then warn "AWS CLI exists"; return; fi
299 log "Installing AWS CLI directly from Amazon..."
300
301 if [[ "$OS" == "macOS" ]]; then
302 curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg"
303 sudo installer -pkg /tmp/AWSCLIV2.pkg -target /
304 rm /tmp/AWSCLIV2.pkg
305 else
306 if ! require_cmd unzip; then
307 err "unzip is required. Please install 'Build Tools' (Option 1) first."
308 return 1
309 fi
310 curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip"
311 cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install
312 rm -rf /tmp/awscliv2.zip /tmp/aws
313 fi
314}
315
316install_gcloud() {
317 if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi
318 log "Installing Google Cloud CLI..."
319 curl -fsSL https://sdk.cloud.google.com | $CURRENT_SHELL -s -- --disable-prompts
320
321 if [[ "$CURRENT_SHELL" == "zsh" ]]; then
322 add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc"'
323 else
324 add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && source "$HOME/google-cloud-sdk/path.bash.inc"'
325 fi
326}
327
328install_firebase() {
329 if require_cmd firebase; then warn "Firebase CLI exists"; return; fi
330 log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..."
331
332 if ! require_cmd npm; then
333 err "NPM not found. Please install NVM (Option 3) first."
334 return 1
335 fi
336
337 npm install -g firebase-tools
338}
339
340# =======================================================
341# DEV TOOLS & SECURITY
342# =======================================================
343
344install_gh() {
345 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
346 log "Fetching latest GitHub CLI version..."
347 LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
348 TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}"
349
350 curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz"
351 tar -xzf /tmp/gh.tar.gz -C /tmp
352 sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/
353 sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz
354}
355
356install_infisical() {
357 if require_cmd infisical; then warn "Infisical CLI exists"; return; fi
358 log "Installing Infisical CLI via NPM..."
359
360 if ! require_cmd npm; then
361 err "NPM not found. Please install NVM (Option 3) first."
362 return 1
363 fi
364
365 npm install -g @infisical/cli
366}
367
368# =======================================================
369# AI & AGENTIC TOOLS
370# =======================================================
371
372install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
373install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
374
375install_codex() {
376 log "Installing @openai/codex..."
377 if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi
378 npm i -g @openai/codex
379}
380
381# =======================================================
382# MENU LOGIC
383# =======================================================
384show_menu() {
385 clear
386 printf "${BLUE}==================================================${NC}\n"
387 printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
388 printf "${BLUE}==================================================${NC}\n"
389 printf "${YELLOW}--- Core Runtimes & Managers ---${NC}\n"
390 printf " 1) Build Tools (GCC/Make)\n"
391 printf " 2) Homebrew (Optional)\n"
392 printf " 3) NVM (Node.js)\n"
393 printf " 4) Python 3 (Official API)\n"
394 printf " 5) Go (Official -> /usr/local)\n"
395 printf " 6) SDKMAN (Java/Kotlin)\n"
396 printf " 7) .NET (Official Script)\n"
397 printf "${YELLOW}--- Cloud & Infrastructure ---${NC}\n"
398 printf " 8) Docker (Official DMG/sh)\n"
399 printf " 9) AWS CLI (Official Bin)\n"
400 printf " 10) Google Cloud CLI (gcloud)\n"
401 printf " 11) Firebase CLI (NPM/ARM64 Safe)\n"
402 printf "${YELLOW}--- Dev Tools & Security ---${NC}\n"
403 printf " 12) GitHub CLI (Official Bin)\n"
404 printf " 13) Infisical (Official Bin)\n"
405 printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
406 printf " 14) Claude Code CLI\n"
407 printf " 15) OpenCode (opencode.ai)\n"
408 printf " 16) OpenAI Codex CLI\n"
409 printf "${BLUE}==================================================${NC}\n"
410 printf " 99) Quit\n"
411 printf "${BLUE}==================================================${NC}\n"
412}
413
414while true; do
415 show_menu
416
417 # Bulletproof prompt for both Bash and Zsh
418 printf "Select options (space-separated): "
419 read input
420
421 # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
422 for choice in $input; do
423 case "$choice" in
424 1) install_build_tools ;;
425 2) install_brew ;;
426 3) install_nvm ;;
427 4) install_python ;;
428 5) install_go ;;
429 6) install_sdkman ;;
430 7) install_dotnet ;;
431 8) install_docker ;;
432 9) install_aws ;;
433 10) install_gcloud ;;
434 11) install_firebase ;;
435 12) install_gh ;;
436 13) install_infisical ;;
437 14) install_claude ;;
438 15) install_opencode ;;
439 16) install_codex ;;
440 99) log "Exiting..."; exit 0 ;;
441 *) warn "Option $choice not valid." ;;
442 esac
443 done
444
445 printf "Press Enter to continue..."
446 read dummy
447done