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
.pkgfor 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 officialget.docker.comscript (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
dockergroup. 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
unzipto extract the payload.
Built for developers who want total control over their toolchain.
| 1 | #!/usr/bin/env bash |
| 2 | set -u |
| 3 | |
| 4 | # ============================= |
| 5 | # COLORS & LOGGING |
| 6 | # ============================= |
| 7 | GREEN="\033[0;32m" |
| 8 | RED="\033[0;31m" |
| 9 | YELLOW="\033[1;33m" |
| 10 | BLUE="\033[0;34m" |
| 11 | NC="\033[0m" |
| 12 | |
| 13 | log() { echo -e "${GREEN}▶ $*${NC}"; } |
| 14 | warn() { echo -e "${YELLOW}⚠ $*${NC}"; } |
| 15 | err() { echo -e "${RED}✖ $*${NC}"; } |
| 16 | info() { echo -e "${BLUE}ℹ $*${NC}"; } |
| 17 | |
| 18 | # ============================= |
| 19 | # HELPERS & SYSTEM DETECTION |
| 20 | # ============================= |
| 21 | require_cmd() { command -v "$1" >/dev/null 2>&1; } |
| 22 | |
| 23 | add_to_path_config() { |
| 24 | local label=$1 |
| 25 | local path_line=$2 |
| 26 | local target_file="${3:-$HOME/.pathrc}" |
| 27 | |
| 28 | if [[ -f "$target_file" ]]; then |
| 29 | if ! grep -q "$label" "$target_file"; then |
| 30 | echo -e "\n# $label\n$path_line" >> "$target_file" |
| 31 | log "Added $label to $target_file" |
| 32 | fi |
| 33 | 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." |
| 37 | fi |
| 38 | } |
| 39 | |
| 40 | detect_os_arch() { |
| 41 | ARCH=$(uname -m) |
| 42 | case "$ARCH" in |
| 43 | x86_64|amd64) SYS_ARCH="amd64"; MAC_ARCH="x86_64"; AWS_ARCH="x86_64" ;; |
| 44 | aarch64|arm64) SYS_ARCH="arm64"; MAC_ARCH="arm64"; AWS_ARCH="aarch64" ;; |
| 45 | *) err "Unsupported architecture: $ARCH"; exit 1 ;; |
| 46 | esac |
| 47 | |
| 48 | if [[ "$OSTYPE" == "darwin"* ]]; then |
| 49 | OS="macOS" |
| 50 | OS_LOWER="darwin" |
| 51 | elif grep -qi microsoft /proc/version 2>/dev/null; then |
| 52 | OS="WSL" |
| 53 | OS_LOWER="linux" |
| 54 | elif [[ -f /etc/os-release ]]; then |
| 55 | . /etc/os-release |
| 56 | [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* ]] && OS="Ubuntu" || OS="Linux" |
| 57 | OS_LOWER="linux" |
| 58 | else |
| 59 | OS="Unknown" |
| 60 | OS_LOWER="unknown" |
| 61 | fi |
| 62 | log "Detected: $OS ($ARCH)" |
| 63 | } |
| 64 | |
| 65 | detect_os_arch |
| 66 | |
| 67 | # ============================= |
| 68 | # CORE RUNTIMES & MANAGERS |
| 69 | # ============================= |
| 70 | |
| 71 | install_build_tools() { |
| 72 | log "Installing Build Essentials & Core Dependencies..." |
| 73 | case "$OS" in |
| 74 | 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 ;; |
| 75 | macOS) xcode-select --install || warn "Xcode tools already installed" ;; |
| 76 | *) warn "Manual installation required for $OS." ;; |
| 77 | esac |
| 78 | } |
| 79 | |
| 80 | install_brew() { |
| 81 | if require_cmd brew; then warn "Homebrew already installed"; return; fi |
| 82 | log "Installing Homebrew from Official Source..." |
| 83 | /bin/bash -c "$(curl -fsSL [https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh](https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh))" |
| 84 | |
| 85 | if [[ "$OS_LOWER" == "linux" ]]; then |
| 86 | add_to_path_config "HOMEBREW" 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' |
| 87 | elif [[ "$MAC_ARCH" == "arm64" ]]; then |
| 88 | add_to_path_config "HOMEBREW" 'eval "$(/opt/homebrew/bin/brew shellenv)"' |
| 89 | else |
| 90 | add_to_path_config "HOMEBREW" 'eval "$(/usr/local/bin/brew shellenv)"' |
| 91 | fi |
| 92 | } |
| 93 | |
| 94 | install_nvm() { |
| 95 | export NVM_DIR="$HOME/.nvm" |
| 96 | |
| 97 | if [ -d "$NVM_DIR" ]; then |
| 98 | warn "NVM is already installed." |
| 99 | else |
| 100 | log "Installing NVM..." |
| 101 | curl -fsSL [https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh](https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh) | bash |
| 102 | fi |
| 103 | |
| 104 | # Load NVM for the current session to ensure the 'nvm' command is available immediately |
| 105 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" |
| 106 | |
| 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 |
| 112 | |
| 113 | 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 | ;; |
| 129 | esac |
| 130 | } |
| 131 | |
| 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](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" |
| 136 | |
| 137 | log "Installing Python $LATEST_PY directly from Python.org..." |
| 138 | if [[ "$OS" == "macOS" ]]; then |
| 139 | PKG_URL="[https://www.python.org/ftp/python/$](https://www.python.org/ftp/python/$){LATEST_PY}/python-${LATEST_PY}-macos11.pkg" |
| 140 | curl -fsSL -o /tmp/python.pkg "$PKG_URL" |
| 141 | sudo installer -pkg /tmp/python.pkg -target / |
| 142 | rm /tmp/python.pkg |
| 143 | else |
| 144 | SRC_URL="[https://www.python.org/ftp/python/$](https://www.python.org/ftp/python/$){LATEST_PY}/Python-${LATEST_PY}.tgz" |
| 145 | curl -fsSL -o /tmp/Python.tgz "$SRC_URL" |
| 146 | cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}" |
| 147 | ./configure --enable-optimizations |
| 148 | sudo make altinstall |
| 149 | |
| 150 | PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2) |
| 151 | log "Symlinking python${PY_MINOR} to python3..." |
| 152 | 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 |
| 154 | |
| 155 | cd ~ && sudo rm -rf /tmp/Python* |
| 156 | fi |
| 157 | } |
| 158 | |
| 159 | install_go() { |
| 160 | log "Fetching latest Go version from official source..." |
| 161 | LATEST_GO=$(curl -sL [https://go.dev/VERSION?m=text](https://go.dev/VERSION?m=text) | head -n 1) |
| 162 | TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz" |
| 163 | |
| 164 | log "Downloading $TAR_FILE..." |
| 165 | curl -fsSL -o /tmp/go.tar.gz "[https://go.dev/dl/$TAR_FILE](https://go.dev/dl/$TAR_FILE)" |
| 166 | |
| 167 | log "Installing to /usr/local/go..." |
| 168 | sudo rm -rf /usr/local/go |
| 169 | sudo tar -C /usr/local -xzf /tmp/go.tar.gz |
| 170 | rm /tmp/go.tar.gz |
| 171 | add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"' |
| 172 | } |
| 173 | |
| 174 | install_sdkman() { |
| 175 | if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi |
| 176 | log "Installing SDKMAN..." |
| 177 | curl -s "[https://get.sdkman.io](https://get.sdkman.io)" | bash |
| 178 | } |
| 179 | |
| 180 | install_dotnet() { |
| 181 | log "Installing .NET from official script..." |
| 182 | curl -fsSL [https://dot.net/v1/dotnet-install.sh](https://dot.net/v1/dotnet-install.sh) | bash |
| 183 | add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"' |
| 184 | } |
| 185 | |
| 186 | # ============================= |
| 187 | # CLOUD & INFRASTRUCTURE |
| 188 | # ============================= |
| 189 | |
| 190 | install_docker() { |
| 191 | if require_cmd docker; then log "Docker exists"; return; fi |
| 192 | log "Installing Docker from official source..." |
| 193 | |
| 194 | if [[ "$OS" == "macOS" ]]; then |
| 195 | [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64" |
| 196 | DMG_URL="[https://desktop.docker.com/mac/main/$](https://desktop.docker.com/mac/main/$){DOCKER_MAC_ARCH}/Docker.dmg" |
| 197 | curl -fsSL -o /tmp/Docker.dmg "$DMG_URL" |
| 198 | hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker |
| 199 | sudo cp -a /Volumes/Docker/Docker.app /Applications/ |
| 200 | hdiutil detach /Volumes/Docker |
| 201 | rm /tmp/Docker.dmg |
| 202 | else |
| 203 | curl -fsSL [https://get.docker.com](https://get.docker.com) | sudo sh |
| 204 | sudo usermod -aG docker "$USER" |
| 205 | log "Added $USER to docker group. You may need to logout/login." |
| 206 | fi |
| 207 | } |
| 208 | |
| 209 | install_aws() { |
| 210 | if require_cmd aws; then warn "AWS CLI exists"; return; fi |
| 211 | log "Installing AWS CLI directly from Amazon..." |
| 212 | |
| 213 | if [[ "$OS" == "macOS" ]]; then |
| 214 | curl -fsSL -o /tmp/AWSCLIV2.pkg "[https://awscli.amazonaws.com/AWSCLIV2.pkg](https://awscli.amazonaws.com/AWSCLIV2.pkg)" |
| 215 | sudo installer -pkg /tmp/AWSCLIV2.pkg -target / |
| 216 | rm /tmp/AWSCLIV2.pkg |
| 217 | else |
| 218 | if ! require_cmd unzip; then |
| 219 | err "unzip is required. Please install 'Build Tools' (Option 1) first." |
| 220 | return 1 |
| 221 | fi |
| 222 | curl -fsSL -o /tmp/awscliv2.zip "[https://awscli.amazonaws.com/awscli-exe-linux-$](https://awscli.amazonaws.com/awscli-exe-linux-$){AWS_ARCH}.zip" |
| 223 | cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install |
| 224 | rm -rf /tmp/awscliv2.zip /tmp/aws |
| 225 | fi |
| 226 | } |
| 227 | |
| 228 | install_gcloud() { |
| 229 | if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi |
| 230 | log "Installing Google Cloud CLI..." |
| 231 | curl -fsSL [https://sdk.cloud.google.com](https://sdk.cloud.google.com) | bash -s -- --disable-prompts |
| 232 | add_to_path_config "GCLOUD" 'source "$HOME/google-cloud-sdk/path.bash.inc"' |
| 233 | } |
| 234 | |
| 235 | install_firebase() { |
| 236 | if require_cmd firebase; then warn "Firebase CLI exists"; return; fi |
| 237 | log "Installing Standalone Firebase CLI..." |
| 238 | curl -sL [https://firebase.tools](https://firebase.tools) | bash |
| 239 | } |
| 240 | |
| 241 | # ============================= |
| 242 | # DEV TOOLS & SECURITY |
| 243 | # ============================= |
| 244 | |
| 245 | install_gh() { |
| 246 | if require_cmd gh; then warn "GitHub CLI exists"; return; fi |
| 247 | log "Fetching latest GitHub CLI version..." |
| 248 | LATEST_GH=$(curl -s [https://api.github.com/repos/cli/cli/releases/latest](https://api.github.com/repos/cli/cli/releases/latest) | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') |
| 249 | TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}" |
| 250 | |
| 251 | curl -fsSL -o /tmp/gh.tar.gz "[https://github.com/cli/cli/releases/download/v$](https://github.com/cli/cli/releases/download/v$){LATEST_GH}/${TAR_NAME}.tar.gz" |
| 252 | tar -xzf /tmp/gh.tar.gz -C /tmp |
| 253 | sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/ |
| 254 | sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz |
| 255 | } |
| 256 | |
| 257 | install_infisical() { |
| 258 | if require_cmd infisical; then warn "Infisical exists"; return; fi |
| 259 | log "Fetching latest Infisical binary from GitHub Releases..." |
| 260 | LATEST_INF=$(curl -s [https://api.github.com/repos/Infisical/infisical-cli/releases/latest](https://api.github.com/repos/Infisical/infisical-cli/releases/latest) | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') |
| 261 | TAR_FILE="infisical_${LATEST_INF}_${OS_LOWER}_${SYS_ARCH}.tar.gz" |
| 262 | |
| 263 | curl -fsSL -o /tmp/infisical.tar.gz "[https://github.com/Infisical/infisical-cli/releases/download/v$](https://github.com/Infisical/infisical-cli/releases/download/v$){LATEST_INF}/${TAR_FILE}" |
| 264 | tar -xzf /tmp/infisical.tar.gz -C /tmp |
| 265 | sudo mv /tmp/infisical /usr/local/bin/ |
| 266 | rm /tmp/infisical.tar.gz |
| 267 | } |
| 268 | |
| 269 | # ============================= |
| 270 | # AI & AGENTIC TOOLS |
| 271 | # ============================= |
| 272 | |
| 273 | install_claude() { log "Installing Claude Code..."; curl -fsSL [https://claude.ai/install.sh](https://claude.ai/install.sh) | bash; } |
| 274 | install_opencode() { log "Installing OpenCode..."; curl -fsSL [https://opencode.ai/install](https://opencode.ai/install) | bash; } |
| 275 | |
| 276 | install_codex() { |
| 277 | log "Installing OpenAI Codex CLI..." |
| 278 | if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi |
| 279 | npm install -g openai |
| 280 | } |
| 281 | |
| 282 | # ============================= |
| 283 | # MENU LOGIC |
| 284 | # ============================= |
| 285 | show_menu() { |
| 286 | clear |
| 287 | echo -e "${BLUE}==================================================${NC}" |
| 288 | echo -e "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Strict) ${NC}" |
| 289 | echo -e "${BLUE}==================================================${NC}" |
| 290 | echo -e "${YELLOW}--- Core Runtimes & Managers ---${NC}" |
| 291 | echo " 1) Build Tools (GCC/Make) 2) Homebrew (Optional)" |
| 292 | echo " 3) NVM (Node.js) 4) Python 3 (Official API)" |
| 293 | echo " 5) Go (Official -> /usr/local) 6) SDKMAN (Java/Kotlin)" |
| 294 | echo " 7) .NET (Official Script)" |
| 295 | echo -e "${YELLOW}--- Cloud & Infrastructure ---${NC}" |
| 296 | echo " 8) Docker (Official DMG/sh) 9) AWS CLI (Official Bin)" |
| 297 | echo " 10) Google Cloud CLI (gcloud) 11) Firebase CLI (Standalone)" |
| 298 | echo -e "${YELLOW}--- Dev Tools & Security ---${NC}" |
| 299 | echo " 12) GitHub CLI (Official Bin) 13) Infisical (Official Bin)" |
| 300 | echo -e "${YELLOW}--- AI & Agentic Tools ---${NC}" |
| 301 | echo " 14) Claude Code CLI 15) OpenCode (opencode.ai)" |
| 302 | echo " 16) OpenAI Codex CLI" |
| 303 | echo -e "${BLUE}==================================================${NC}" |
| 304 | echo " 99) Quit" |
| 305 | echo -e "${BLUE}==================================================${NC}" |
| 306 | } |
| 307 | |
| 308 | while true; do |
| 309 | show_menu |
| 310 | read -p "Select options (space-separated): " input |
| 311 | for choice in $input; do |
| 312 | case "$choice" in |
| 313 | 1) install_build_tools ;; |
| 314 | 2) install_brew ;; |
| 315 | 3) install_nvm ;; |
| 316 | 4) install_python ;; |
| 317 | 5) install_go ;; |
| 318 | 6) install_sdkman ;; |
| 319 | 7) install_dotnet ;; |
| 320 | 8) install_docker ;; |
| 321 | 9) install_aws ;; |
| 322 | 10) install_gcloud ;; |
| 323 | 11) install_firebase ;; |
| 324 | 12) install_gh ;; |
| 325 | 13) install_infisical ;; |
| 326 | 14) install_claude ;; |
| 327 | 15) install_opencode ;; |
| 328 | 16) install_codex ;; |
| 329 | 99) log "Exiting..."; exit 0 ;; |
| 330 | *) warn "Option $choice not valid." ;; |
| 331 | esac |
| 332 | done |
| 333 | read -p "Press Enter to continue..." |
| 334 | done |