Последняя активность 3 weeks ago

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

Vernon Wee Hong KOH ревизий этого фрагмента 3 weeks ago. К ревизии

1 file changed, 39 insertions, 9 deletions

menu.sh

@@ -184,35 +184,65 @@ install_nvm() {
184 184 install_python() {
185 185 log "Fetching latest stable Python version..."
186 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"
187 + LATEST_PY=$(
188 + curl -fsSL --compressed https://www.python.org/downloads/ |
189 + sed -nE 's/.*Download Python ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' |
190 + head -1
191 + )
192 +
193 + if [[ -z "$LATEST_PY" ]]; then
194 + err "Could not detect the latest stable Python version from python.org."
195 + return 1
196 + fi
189 197
190 198 log "Detected Stable Version: $LATEST_PY"
191 199
192 200 if [[ "$OS" == "macOS" ]]; then
193 201 log "Downloading official macOS package..."
194 202 PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg"
195 - curl -fsSL -o /tmp/python.pkg "$PKG_URL"
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 + }
196 207
197 208 log "Running installer..."
198 - sudo installer -pkg /tmp/python.pkg -target /
209 + sudo installer -pkg /tmp/python.pkg -target / || {
210 + err "Python macOS installer failed."
211 + rm -f /tmp/python.pkg
212 + return 1
213 + }
199 214 rm /tmp/python.pkg
200 215 else
201 216 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
217 + sudo apt-get update && sudo apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev libsqlite3-dev || {
218 + err "Failed to install Python build dependencies."
219 + return 1
220 + }
203 221
204 222 SRC_URL="https://www.python.org/ftp/python/${LATEST_PY}/Python-${LATEST_PY}.tgz"
205 223
206 224 log "Downloading $SRC_URL..."
207 - curl -fSL -o /tmp/Python.tgz "$SRC_URL" || err "Failed to download Python source from $SRC_URL"
225 + curl -fSL --compressed -o /tmp/Python.tgz "$SRC_URL" || {
226 + err "Failed to download Python source from $SRC_URL"
227 + return 1
228 + }
208 229
209 - cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}"
230 + cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}" || {
231 + err "Failed to extract Python source archive."
232 + return 1
233 + }
210 234
211 235 log "Configuring build (with ensurepip)..."
212 - ./configure --enable-optimizations --with-ensurepip=install
236 + ./configure --enable-optimizations --with-ensurepip=install || {
237 + err "Python configure step failed."
238 + return 1
239 + }
213 240
214 241 log "Compiling Python (this may take a few minutes)..."
215 - sudo make altinstall
242 + sudo make altinstall || {
243 + err "Python build/install step failed."
244 + return 1
245 + }
216 246
217 247 PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2)
218 248

weehong ревизий этого фрагмента 3 weeks ago. К ревизии

Без изменений

weehong ревизий этого фрагмента 3 weeks ago. К ревизии

1 file changed, 94 insertions, 19 deletions

menu.sh

@@ -267,6 +267,79 @@ install_dotnet() {
267 267 add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"'
268 268 }
269 269
270 + install_android() {
271 + log "Installing Android SDK command-line tools..."
272 +
273 + if ! require_cmd unzip; then
274 + err "unzip is required. Please install 'Build Tools' (Option 1) first."
275 + return 1
276 + fi
277 +
278 + if [[ "$OS_LOWER" == "linux" && "$SYS_ARCH" != "amd64" ]]; then
279 + err "Google's Android command-line tools installer supports Linux x86_64 only."
280 + return 1
281 + fi
282 +
283 + ANDROID_HOME="$HOME/Android/Sdk"
284 + ANDROID_SDK_ROOT="$ANDROID_HOME"
285 + export ANDROID_HOME ANDROID_SDK_ROOT
286 +
287 + mkdir -p "$ANDROID_HOME/cmdline-tools"
288 +
289 + if [[ "$OS" == "macOS" ]]; then
290 + TOOLS_OS="mac"
291 + elif [[ "$OS_LOWER" == "linux" ]]; then
292 + TOOLS_OS="linux"
293 + else
294 + err "Android SDK installation is not supported for $OS."
295 + return 1
296 + fi
297 +
298 + log "Finding latest Android command-line tools from Google..."
299 + TOOLS_URL=$(curl -fsSL https://developer.android.com/studio \
300 + | grep -o "https://dl.google.com/android/repository/commandlinetools-${TOOLS_OS}-[0-9]*_latest.zip" \
301 + | head -1)
302 +
303 + if [[ -z "$TOOLS_URL" ]]; then
304 + err "Could not find the Android command-line tools download URL."
305 + return 1
306 + fi
307 +
308 + log "Downloading Android command-line tools..."
309 + rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
310 + mkdir -p /tmp/android-cmdline-tools
311 + curl -fsSL -o /tmp/android-cmdline-tools.zip "$TOOLS_URL"
312 + unzip -q /tmp/android-cmdline-tools.zip -d /tmp/android-cmdline-tools
313 +
314 + rm -rf "$ANDROID_HOME/cmdline-tools/latest"
315 + mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
316 + mv /tmp/android-cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
317 + rm -rf /tmp/android-cmdline-tools /tmp/android-cmdline-tools.zip
318 +
319 + SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
320 + if [[ ! -x "$SDKMANAGER" ]]; then
321 + err "sdkmanager was not installed correctly."
322 + return 1
323 + fi
324 +
325 + log "Accepting Android SDK licenses..."
326 + yes | "$SDKMANAGER" --sdk_root="$ANDROID_HOME" --licenses >/dev/null || true
327 +
328 + log "Installing Android SDK packages..."
329 + "$SDKMANAGER" --sdk_root="$ANDROID_HOME" \
330 + "cmdline-tools;latest" \
331 + "platform-tools" \
332 + "emulator" \
333 + "platforms;android-36" \
334 + "build-tools;36.0.0"
335 +
336 + add_to_path_config "ANDROID_SDK" 'export ANDROID_HOME="$HOME/Android/Sdk"
337 + export ANDROID_SDK_ROOT="$ANDROID_HOME"
338 + export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"'
339 +
340 + log "Android SDK installation sequence finished!"
341 + }
342 +
270 343 # =======================================================
271 344 # CLOUD & INFRASTRUCTURE
272 345 # =======================================================
@@ -428,18 +501,19 @@ show_menu() {
428 501 printf " 5) Go (Official -> /usr/local)\n"
429 502 printf " 6) SDKMAN (Java/Kotlin)\n"
430 503 printf " 7) .NET (Official Script)\n"
504 + printf " 8) Android SDK (CLI + Platform Tools)\n"
431 505 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"
506 + printf " 9) Docker (Official DMG/sh)\n"
507 + printf " 10) AWS CLI (Official Bin)\n"
508 + printf " 11) Google Cloud CLI (gcloud)\n"
509 + printf " 12) Firebase CLI (NPM/ARM64 Safe)\n"
436 510 printf "${YELLOW}--- Dev Tools & Security ---${NC}\n"
437 - printf " 12) GitHub CLI (Official Bin)\n"
438 - printf " 13) Infisical (Official Bin)\n"
511 + printf " 13) GitHub CLI (Official Bin)\n"
512 + printf " 14) Infisical (Official Bin)\n"
439 513 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"
514 + printf " 15) Claude Code CLI\n"
515 + printf " 16) OpenCode (opencode.ai)\n"
516 + printf " 17) OpenAI Codex CLI\n"
443 517 printf "${BLUE}==================================================${NC}\n"
444 518 printf " 99) Quit & Refresh Shell\n"
445 519 printf "${BLUE}==================================================${NC}\n"
@@ -462,15 +536,16 @@ while true; do
462 536 5) install_go ;;
463 537 6) install_sdkman ;;
464 538 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 ;;
539 + 8) install_android ;;
540 + 9) install_docker ;;
541 + 10) install_aws ;;
542 + 11) install_gcloud ;;
543 + 12) install_firebase ;;
544 + 13) install_gh ;;
545 + 14) install_infisical ;;
546 + 15) install_claude ;;
547 + 16) install_opencode ;;
548 + 17) install_codex ;;
474 549 99)
475 550 log "Installation complete! Refreshing terminal environment..."
476 551
@@ -487,4 +562,4 @@ while true; do
487 562
488 563 printf "Press Enter to continue..."
489 564 read dummy
490 - done
565 + done

weehong ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 26 insertions, 9 deletions

menu.sh

@@ -272,9 +272,11 @@ install_dotnet() {
272 272 # =======================================================
273 273
274 274 install_docker() {
275 - if require_cmd docker; then log "Docker exists"; else
275 + log "Checking Docker status..."
276 +
277 + # 1. Install if missing
278 + if ! require_cmd docker; then
276 279 log "Installing Docker from official source..."
277 -
278 280 if [[ "$OS" == "macOS" ]]; then
279 281 [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64"
280 282 DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg"
@@ -290,29 +292,39 @@ install_docker() {
290 292 else
291 293 curl -fsSL https://get.docker.com | sudo sh
292 294 fi
295 + else
296 + warn "Docker is already installed. Enforcing permissions..."
293 297 fi
294 298
299 + # 2. Aggressively enforce Linux permissions
295 300 if [[ "$OS" != "macOS" ]]; then
296 - log "Applying Linux post-install actions..."
301 + log "Applying strict Linux post-install actions..."
297 302
303 + # Ensure docker group exists and user is added
298 304 sudo groupadd -f docker
299 305 sudo usermod -aG docker "$USER"
300 306
307 + # Ensure services are enabled and running
301 308 if command -v systemctl >/dev/null 2>&1; then
302 309 sudo systemctl enable --now docker.service
303 - sudo systemctl enable --now containerd.service
310 + sudo systemctl enable --now docker.socket containerd.service 2>/dev/null || true
304 311 elif [[ "$OS" == "WSL" ]]; then
305 312 sudo service docker start
306 313 fi
307 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
308 319 if [[ -S /var/run/docker.sock ]]; then
309 - log "Fixing ownership of /var/run/docker.sock..."
320 + log "Securing /var/run/docker.sock..."
310 321 sudo chown root:docker /var/run/docker.sock
311 322 sudo chmod 660 /var/run/docker.sock
323 + else
324 + warn "Docker socket not found. The service may have failed to start."
312 325 fi
313 326
314 - log "Added $USER to the docker group."
315 - log "CRITICAL: To apply group changes immediately, run: newgrp docker"
327 + log "Added $USER to the docker group and secured the socket."
316 328 fi
317 329 }
318 330
@@ -461,8 +473,13 @@ while true; do
461 473 16) install_codex ;;
462 474 99)
463 475 log "Installation complete! Refreshing terminal environment..."
464 - # $SHELL holds the user's actual default terminal (e.g., /bin/zsh)
465 - exec "${SHELL:-zsh}"
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
466 483 ;;
467 484 *) warn "Option $choice not valid." ;;
468 485 esac

weehong ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 2 insertions, 1 deletion

menu.sh

@@ -461,7 +461,8 @@ while true; do
461 461 16) install_codex ;;
462 462 99)
463 463 log "Installation complete! Refreshing terminal environment..."
464 - exec "$CURRENT_SHELL"
464 + # $SHELL holds the user's actual default terminal (e.g., /bin/zsh)
465 + exec "${SHELL:-zsh}"
465 466 ;;
466 467 *) warn "Option $choice not valid." ;;
467 468 esac

weehong ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 30 insertions, 5 deletions

menu.sh

@@ -74,6 +74,10 @@ add_to_path_config() {
74 74 printf "\n# %s\n%s\n" "$label" "$path_line" >> "$PROFILE_FILE"
75 75 log "Added $label to shell profile ($PROFILE_FILE)."
76 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
77 81 }
78 82
79 83 detect_os_arch() {
@@ -153,9 +157,27 @@ install_nvm() {
153 157 read node_choice
154 158
155 159 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 ;;
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 + ;;
159 181 esac
160 182 }
161 183
@@ -407,7 +429,7 @@ show_menu() {
407 429 printf " 15) OpenCode (opencode.ai)\n"
408 430 printf " 16) OpenAI Codex CLI\n"
409 431 printf "${BLUE}==================================================${NC}\n"
410 - printf " 99) Quit\n"
432 + printf " 99) Quit & Refresh Shell\n"
411 433 printf "${BLUE}==================================================${NC}\n"
412 434 }
413 435
@@ -437,7 +459,10 @@ while true; do
437 459 14) install_claude ;;
438 460 15) install_opencode ;;
439 461 16) install_codex ;;
440 - 99) log "Exiting..."; exit 0 ;;
462 + 99)
463 + log "Installation complete! Refreshing terminal environment..."
464 + exec "$CURRENT_SHELL"
465 + ;;
441 466 *) warn "Option $choice not valid." ;;
442 467 esac
443 468 done

weehong ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 10 insertions, 2 deletions

menu.sh

@@ -227,8 +227,16 @@ install_go() {
227 227
228 228 install_sdkman() {
229 229 if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi
230 - log "Installing SDKMAN via $CURRENT_SHELL..."
231 - curl -s "https://get.sdkman.io" | $CURRENT_SHELL
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
232 240 }
233 241
234 242 install_dotnet() {

weehong ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 13 insertions, 7 deletions

menu.sh

@@ -5,13 +5,19 @@
5 5 # =======================================================
6 6 if [ -z "${_PREFER_ZSH_BOOTSTRAPPED:-}" ]; then
7 7 export _PREFER_ZSH_BOOTSTRAPPED=1
8 - if command -v zsh >/dev/null 2>&1; then
9 - exec zsh "$0" "$@"
10 - elif command -v bash >/dev/null 2>&1; then
11 - exec bash "$0" "$@"
12 - else
13 - echo "Error: Neither Zsh nor Bash is installed. Exiting."
14 - exit 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
15 21 fi
16 22 fi
17 23

weehong ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 117 insertions, 90 deletions

menu.sh

@@ -1,39 +1,72 @@
1 1 #!/usr/bin/env bash
2 +
3 + # =======================================================
4 + # 1. BOOTSTRAPPER: PREFER ZSH, FALLBACK TO BASH
5 + # =======================================================
6 + if [ -z "${_PREFER_ZSH_BOOTSTRAPPED:-}" ]; then
7 + export _PREFER_ZSH_BOOTSTRAPPED=1
8 + if command -v zsh >/dev/null 2>&1; then
9 + exec zsh "$0" "$@"
10 + elif command -v bash >/dev/null 2>&1; then
11 + exec bash "$0" "$@"
12 + else
13 + echo "Error: Neither Zsh nor Bash is installed. Exiting."
14 + exit 1
15 + fi
16 + fi
17 +
2 18 set -u
3 19
4 - # =============================
20 + # =======================================================
21 + # 2. CROSS-SHELL NORMALIZATION
22 + # =======================================================
23 + if [ -n "${ZSH_VERSION:-}" ]; then
24 + # Zsh: Enable Bash-like word splitting for unquoted variables (menu input)
25 + setopt shwordsplit 2>/dev/null || true
26 + CURRENT_SHELL="zsh"
27 + PROFILE_FILE="$HOME/.zshrc"
28 + elif [ -n "${BASH_VERSION:-}" ]; then
29 + # Bash
30 + CURRENT_SHELL="bash"
31 + PROFILE_FILE="$HOME/.bashrc"
32 + else
33 + # Fallback POSIX
34 + CURRENT_SHELL="sh"
35 + PROFILE_FILE="$HOME/.profile"
36 + fi
37 +
38 + # =======================================================
5 39 # COLORS & LOGGING
6 - # =============================
40 + # =======================================================
7 41 GREEN="\033[0;32m"
8 42 RED="\033[0;31m"
9 43 YELLOW="\033[1;33m"
10 44 BLUE="\033[0;34m"
11 45 NC="\033[0m"
12 46
13 - log() { echo -e "${GREEN}▶ $*${NC}"; }
14 - warn() { echo -e "${YELLOW}⚠ $*${NC}"; }
15 - err() { echo -e "${RED}✖ $*${NC}"; }
16 - info() { echo -e "${BLUE}ℹ $*${NC}"; }
47 + log() { printf "${GREEN}▶ %s${NC}\n" "$*"; }
48 + warn() { printf "${YELLOW}⚠ %s${NC}\n" "$*"; }
49 + err() { printf "${RED}✖ %s${NC}\n" "$*"; }
50 + info() { printf "${BLUE}ℹ %s${NC}\n" "$*"; }
17 51
18 - # =============================
52 + # =======================================================
19 53 # HELPERS & SYSTEM DETECTION
20 - # =============================
54 + # =======================================================
21 55 require_cmd() { command -v "$1" >/dev/null 2>&1; }
22 56
23 57 add_to_path_config() {
24 58 local label=$1
25 59 local path_line=$2
26 - local target_file="${3:-$HOME/.pathrc}"
60 + local target_file="${3:-$PROFILE_FILE}"
27 61
28 62 if [[ -f "$target_file" ]]; then
29 63 if ! grep -q "$label" "$target_file"; then
30 - echo -e "\n# $label\n$path_line" >> "$target_file"
64 + printf "\n# %s\n%s\n" "$label" "$path_line" >> "$target_file"
31 65 log "Added $label to $target_file"
32 66 fi
33 67 else
34 - echo -e "\n# $label\n$path_line" >> "$HOME/.bashrc"
35 - [[ -f "$HOME/.zshrc" ]] && echo -e "\n# $label\n$path_line" >> "$HOME/.zshrc"
36 - log "Added $label to shell profiles."
68 + printf "\n# %s\n%s\n" "$label" "$path_line" >> "$PROFILE_FILE"
69 + log "Added $label to shell profile ($PROFILE_FILE)."
37 70 fi
38 71 }
39 72
@@ -59,14 +92,14 @@ detect_os_arch() {
59 92 OS="Unknown"
60 93 OS_LOWER="unknown"
61 94 fi
62 - log "Detected: $OS ($ARCH)"
95 + log "Detected: $OS ($ARCH) running $CURRENT_SHELL"
63 96 }
64 97
65 98 detect_os_arch
66 99
67 - # =============================
100 + # =======================================================
68 101 # CORE RUNTIMES & MANAGERS
69 - # =============================
102 + # =======================================================
70 103
71 104 install_build_tools() {
72 105 log "Installing Build Essentials & Core Dependencies..."
@@ -80,6 +113,8 @@ install_build_tools() {
80 113 install_brew() {
81 114 if require_cmd brew; then warn "Homebrew already installed"; return; fi
82 115 log "Installing Homebrew from Official Source..."
116 +
117 + # Homebrew's installer specifically requires Bash execution, regardless of current shell
83 118 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
84 119
85 120 if [[ "$OS_LOWER" == "linux" ]]; then
@@ -97,45 +132,31 @@ install_nvm() {
97 132 if [ -d "$NVM_DIR" ]; then
98 133 warn "NVM is already installed."
99 134 else
100 - log "Installing NVM..."
101 - curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
135 + log "Installing NVM via $CURRENT_SHELL..."
136 + curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | $CURRENT_SHELL
102 137 fi
103 138
104 - # Load NVM for the current session to ensure the 'nvm' command is available immediately
105 139 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
106 140
107 - echo -e "\n${YELLOW}Which version of Node.js would you like to install?${NC}"
108 - echo " 1) LTS (Long Term Support - Recommended for stability)"
109 - echo " 2) Latest (Current features - Recommended for testing new APIs)"
110 - echo " 3) Skip installing Node.js right now"
111 - read -p "Select (1/2/3): " node_choice
141 + printf "\n${YELLOW}Which version of Node.js would you like to install?${NC}\n"
142 + printf " 1) LTS (Long Term Support - Recommended for stability)\n"
143 + printf " 2) Latest (Current features - Recommended for testing new APIs)\n"
144 + printf " 3) Skip installing Node.js right now\n"
145 +
146 + printf "Select (1/2/3): "
147 + read node_choice
112 148
113 149 case "$node_choice" in
114 - 2)
115 - log "Installing Latest Node.js..."
116 - nvm install node
117 - nvm alias default node
118 - nvm use node
119 - ;;
120 - 3)
121 - log "Skipping Node.js installation."
122 - ;;
123 - *)
124 - log "Installing Latest LTS Node.js..."
125 - nvm install --lts
126 - nvm alias default 'lts/*'
127 - nvm use --lts
128 - ;;
150 + 2) log "Installing Latest Node.js..."; nvm install node; nvm alias default node; nvm use node ;;
151 + 3) log "Skipping Node.js installation." ;;
152 + *) log "Installing Latest LTS Node.js..."; nvm install --lts; nvm alias default 'lts/*'; nvm use --lts ;;
129 153 esac
130 154 }
131 155
132 156 install_python() {
133 157 log "Fetching latest stable Python version..."
134 158
135 - # Scrape the main downloads page for the official 'Download' button text
136 159 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\}')
137 -
138 - # Fallback just in case curl fails
139 160 [[ -z "$LATEST_PY" ]] && LATEST_PY="3.12.3"
140 161
141 162 log "Detected Stable Version: $LATEST_PY"
@@ -200,19 +221,19 @@ install_go() {
200 221
201 222 install_sdkman() {
202 223 if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi
203 - log "Installing SDKMAN..."
204 - curl -s "https://get.sdkman.io" | bash
224 + log "Installing SDKMAN via $CURRENT_SHELL..."
225 + curl -s "https://get.sdkman.io" | $CURRENT_SHELL
205 226 }
206 227
207 228 install_dotnet() {
208 229 log "Installing .NET from official script..."
209 - curl -fsSL https://dot.net/v1/dotnet-install.sh | bash
230 + curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL
210 231 add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"'
211 232 }
212 233
213 - # =============================
234 + # =======================================================
214 235 # CLOUD & INFRASTRUCTURE
215 - # =============================
236 + # =======================================================
216 237
217 238 install_docker() {
218 239 if require_cmd docker; then log "Docker exists"; else
@@ -231,20 +252,16 @@ install_docker() {
231 252 open /Applications/Docker.app
232 253 log "Please complete the setup in the Docker Desktop UI."
233 254 else
234 - # Linux install
235 255 curl -fsSL https://get.docker.com | sudo sh
236 256 fi
237 257 fi
238 258
239 - # Post-install logic for Linux (Ubuntu/WSL)
240 259 if [[ "$OS" != "macOS" ]]; then
241 260 log "Applying Linux post-install actions..."
242 261
243 - # 1. Create docker group and add user
244 262 sudo groupadd -f docker
245 263 sudo usermod -aG docker "$USER"
246 264
247 - # 2. Enable and start Docker services
248 265 if command -v systemctl >/dev/null 2>&1; then
249 266 sudo systemctl enable --now docker.service
250 267 sudo systemctl enable --now containerd.service
@@ -252,7 +269,6 @@ install_docker() {
252 269 sudo service docker start
253 270 fi
254 271
255 - # 3. Immediate Socket Fix
256 272 if [[ -S /var/run/docker.sock ]]; then
257 273 log "Fixing ownership of /var/run/docker.sock..."
258 274 sudo chown root:docker /var/run/docker.sock
@@ -286,9 +302,13 @@ install_aws() {
286 302 install_gcloud() {
287 303 if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi
288 304 log "Installing Google Cloud CLI..."
289 - curl -fsSL https://sdk.cloud.google.com | bash -s -- --disable-prompts
305 + curl -fsSL https://sdk.cloud.google.com | $CURRENT_SHELL -s -- --disable-prompts
290 306
291 - add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && . "$HOME/google-cloud-sdk/path.bash.inc"'
307 + if [[ "$CURRENT_SHELL" == "zsh" ]]; then
308 + add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc"'
309 + else
310 + add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && source "$HOME/google-cloud-sdk/path.bash.inc"'
311 + fi
292 312 }
293 313
294 314 install_firebase() {
@@ -303,9 +323,9 @@ install_firebase() {
303 323 npm install -g firebase-tools
304 324 }
305 325
306 - # =============================
326 + # =======================================================
307 327 # DEV TOOLS & SECURITY
308 - # =============================
328 + # =======================================================
309 329
310 330 install_gh() {
311 331 if require_cmd gh; then warn "GitHub CLI exists"; return; fi
@@ -331,12 +351,12 @@ install_infisical() {
331 351 npm install -g @infisical/cli
332 352 }
333 353
334 - # =============================
354 + # =======================================================
335 355 # AI & AGENTIC TOOLS
336 - # =============================
356 + # =======================================================
337 357
338 - install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | bash; }
339 - install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | bash; }
358 + install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; }
359 + install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; }
340 360
341 361 install_codex() {
342 362 log "Installing @openai/codex..."
@@ -344,42 +364,47 @@ install_codex() {
344 364 npm i -g @openai/codex
345 365 }
346 366
347 - # =============================
367 + # =======================================================
348 368 # MENU LOGIC
349 - # =============================
369 + # =======================================================
350 370 show_menu() {
351 371 clear
352 - echo -e "${BLUE}==================================================${NC}"
353 - echo -e "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Strict) ${NC}"
354 - echo -e "${BLUE}==================================================${NC}"
355 - echo -e "${YELLOW}--- Core Runtimes & Managers ---${NC}"
356 - echo " 1) Build Tools (GCC/Make)"
357 - echo " 2) Homebrew (Optional)"
358 - echo " 3) NVM (Node.js)"
359 - echo " 4) Python 3 (Official API)"
360 - echo " 5) Go (Official -> /usr/local)"
361 - echo " 6) SDKMAN (Java/Kotlin)"
362 - echo " 7) .NET (Official Script)"
363 - echo -e "${YELLOW}--- Cloud & Infrastructure ---${NC}"
364 - echo " 8) Docker (Official DMG/sh)"
365 - echo " 9) AWS CLI (Official Bin)"
366 - echo " 10) Google Cloud CLI (gcloud)"
367 - echo " 11) Firebase CLI (NPM/ARM64 Safe)"
368 - echo -e "${YELLOW}--- Dev Tools & Security ---${NC}"
369 - echo " 12) GitHub CLI (Official Bin)"
370 - echo " 13) Infisical (Official Bin)"
371 - echo -e "${YELLOW}--- AI & Agentic Tools ---${NC}"
372 - echo " 14) Claude Code CLI"
373 - echo " 15) OpenCode (opencode.ai)"
374 - echo " 16) OpenAI Codex CLI"
375 - echo -e "${BLUE}==================================================${NC}"
376 - echo " 99) Quit"
377 - echo -e "${BLUE}==================================================${NC}"
372 + printf "${BLUE}==================================================${NC}\n"
373 + printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n"
374 + printf "${BLUE}==================================================${NC}\n"
375 + printf "${YELLOW}--- Core Runtimes & Managers ---${NC}\n"
376 + printf " 1) Build Tools (GCC/Make)\n"
377 + printf " 2) Homebrew (Optional)\n"
378 + printf " 3) NVM (Node.js)\n"
379 + printf " 4) Python 3 (Official API)\n"
380 + printf " 5) Go (Official -> /usr/local)\n"
381 + printf " 6) SDKMAN (Java/Kotlin)\n"
382 + printf " 7) .NET (Official Script)\n"
383 + printf "${YELLOW}--- Cloud & Infrastructure ---${NC}\n"
384 + printf " 8) Docker (Official DMG/sh)\n"
385 + printf " 9) AWS CLI (Official Bin)\n"
386 + printf " 10) Google Cloud CLI (gcloud)\n"
387 + printf " 11) Firebase CLI (NPM/ARM64 Safe)\n"
388 + printf "${YELLOW}--- Dev Tools & Security ---${NC}\n"
389 + printf " 12) GitHub CLI (Official Bin)\n"
390 + printf " 13) Infisical (Official Bin)\n"
391 + printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n"
392 + printf " 14) Claude Code CLI\n"
393 + printf " 15) OpenCode (opencode.ai)\n"
394 + printf " 16) OpenAI Codex CLI\n"
395 + printf "${BLUE}==================================================${NC}\n"
396 + printf " 99) Quit\n"
397 + printf "${BLUE}==================================================${NC}\n"
378 398 }
379 399
380 400 while true; do
381 401 show_menu
382 - read -p "Select options (space-separated): " input
402 +
403 + # Bulletproof prompt for both Bash and Zsh
404 + printf "Select options (space-separated): "
405 + read input
406 +
407 + # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells
383 408 for choice in $input; do
384 409 case "$choice" in
385 410 1) install_build_tools ;;
@@ -402,5 +427,7 @@ while true; do
402 427 *) warn "Option $choice not valid." ;;
403 428 esac
404 429 done
405 - read -p "Press Enter to continue..."
430 +
431 + printf "Press Enter to continue..."
432 + read dummy
406 433 done

weehong ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 35 insertions, 8 deletions

menu.sh

@@ -130,30 +130,57 @@ install_nvm() {
130 130 }
131 131
132 132 install_python() {
133 - log "Fetching latest Python 3 version from official API..."
134 - LATEST_PY=$(curl -s https://api.github.com/repos/python/cpython/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
135 - [[ -z "$LATEST_PY" ]] && LATEST_PY="3.12.3"
133 + log "Fetching latest stable Python version..."
136 134
137 - log "Installing Python $LATEST_PY directly from Python.org..."
135 + # Scrape the main downloads page for the official 'Download' button text
136 + 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\}')
137 +
138 + # Fallback just in case curl fails
139 + [[ -z "$LATEST_PY" ]] && LATEST_PY="3.12.3"
140 +
141 + log "Detected Stable Version: $LATEST_PY"
142 +
138 143 if [[ "$OS" == "macOS" ]]; then
144 + log "Downloading official macOS package..."
139 145 PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg"
140 146 curl -fsSL -o /tmp/python.pkg "$PKG_URL"
147 +
148 + log "Running installer..."
141 149 sudo installer -pkg /tmp/python.pkg -target /
142 150 rm /tmp/python.pkg
143 151 else
152 + log "Installing build dependencies (zlib, ssl, etc)..."
153 + sudo apt-get update && sudo apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev libsqlite3-dev
154 +
144 155 SRC_URL="https://www.python.org/ftp/python/${LATEST_PY}/Python-${LATEST_PY}.tgz"
145 - curl -fsSL -o /tmp/Python.tgz "$SRC_URL"
156 +
157 + log "Downloading $SRC_URL..."
158 + curl -fSL -o /tmp/Python.tgz "$SRC_URL" || err "Failed to download Python source from $SRC_URL"
159 +
146 160 cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}"
147 - ./configure --enable-optimizations
161 +
162 + log "Configuring build (with ensurepip)..."
163 + ./configure --enable-optimizations --with-ensurepip=install
164 +
165 + log "Compiling Python (this may take a few minutes)..."
148 166 sudo make altinstall
149 167
150 168 PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2)
151 - log "Symlinking python${PY_MINOR} to python3..."
169 +
170 + log "Setting up symlinks..."
152 171 sudo ln -sf /usr/local/bin/python${PY_MINOR} /usr/local/bin/python3
153 - sudo ln -sf /usr/local/bin/pip${PY_MINOR} /usr/local/bin/pip3
172 +
173 + if [[ -f "/usr/local/bin/pip${PY_MINOR}" ]]; then
174 + sudo ln -sf /usr/local/bin/pip${PY_MINOR} /usr/local/bin/pip3
175 + log "Successfully linked pip3!"
176 + else
177 + err "pip${PY_MINOR} was not generated during the build!"
178 + fi
154 179
155 180 cd ~ && sudo rm -rf /tmp/Python*
156 181 fi
182 +
183 + log "Python $LATEST_PY installation sequence finished!"
157 184 }
158 185
159 186 install_go() {
Новее Позже