Last active 3 weeks ago

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

Revision 472fb1c8c172013a53ba5ec17c1d2637e2dafe9e

README.md Raw

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

  1. Download the script: Save the script to your machine as install.sh.

  2. Make it executable:

    chmod +x install.sh
    
  3. Run the installer:

    ./install.sh
    
  4. Follow the interactive menu: You can select multiple tools at once by entering space-separated numbers (e.g., 1 3 8 12).

🛠️ Included Tools

Core Runtimes & Managers

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

Cloud & Infrastructure

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

Dev Tools & Security

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

AI & Agentic Tools

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

⚠️ Important Notes & Troubleshooting

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

Built for developers who want total control over their toolchain.

menu.sh Raw
1#!/usr/bin/env bash
2set -u
3
4# =============================
5# COLORS & LOGGING
6# =============================
7GREEN="\033[0;32m"
8RED="\033[0;31m"
9YELLOW="\033[1;33m"
10BLUE="\033[0;34m"
11NC="\033[0m"
12
13log() { echo -e "${GREEN}$*${NC}"; }
14warn() { echo -e "${YELLOW}$*${NC}"; }
15err() { echo -e "${RED}$*${NC}"; }
16info() { echo -e "${BLUE}$*${NC}"; }
17
18# =============================
19# HELPERS & SYSTEM DETECTION
20# =============================
21require_cmd() { command -v "$1" >/dev/null 2>&1; }
22
23add_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
40detect_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
65detect_os_arch
66
67# =============================
68# CORE RUNTIMES & MANAGERS
69# =============================
70
71install_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
80install_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
94install_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
132install_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
159install_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
174install_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
180install_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
190install_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
209install_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
228install_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
235install_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
245install_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
257install_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
273install_claude() { log "Installing Claude Code..."; curl -fsSL [https://claude.ai/install.sh](https://claude.ai/install.sh) | bash; }
274install_opencode() { log "Installing OpenCode..."; curl -fsSL [https://opencode.ai/install](https://opencode.ai/install) | bash; }
275
276install_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# =============================
285show_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
308while 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..."
334done