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() { | |