|
#!/usr/bin/env bash |
|
set -u |
|
|
|
# ============================= |
|
# COLORS & LOGGING |
|
# ============================= |
|
GREEN="\033[0;32m" |
|
RED="\033[0;31m" |
|
YELLOW="\033[1;33m" |
|
BLUE="\033[0;34m" |
|
NC="\033[0m" |
|
|
|
log() { echo -e "${GREEN}▶ $*${NC}"; } |
|
warn() { echo -e "${YELLOW}⚠ $*${NC}"; } |
|
err() { echo -e "${RED}✖ $*${NC}"; } |
|
info() { echo -e "${BLUE}ℹ $*${NC}"; } |
|
|
|
# ============================= |
|
# HELPERS |
|
# ============================= |
|
require_cmd() { |
|
command -v "$1" >/dev/null 2>&1 || { |
|
return 1 |
|
} |
|
} |
|
|
|
require_node() { |
|
require_cmd node || { |
|
warn "Node.js not found. Install NVM (Option 2) first, then restart your shell." |
|
return 1 |
|
} |
|
} |
|
|
|
cleanup_python_tmp() { |
|
if ls /tmp/Python-* >/dev/null 2>&1; then |
|
warn "Cleaning leftover Python build files..." |
|
sudo rm -rf /tmp/Python-* || true |
|
log "Python build cleanup completed." |
|
fi |
|
} |
|
|
|
# ============================= |
|
# OS DETECTION |
|
# ============================= |
|
detect_os() { |
|
if [[ "$OSTYPE" == "darwin"* ]]; then |
|
echo "macOS" |
|
elif grep -qi microsoft /proc/version 2>/dev/null; then |
|
echo "WSL" |
|
elif [[ -f /etc/os-release ]]; then |
|
. /etc/os-release |
|
[[ "$ID" == "ubuntu" ]] && echo "Ubuntu" || echo "Linux" |
|
else |
|
echo "Unknown" |
|
fi |
|
} |
|
|
|
OS=$(detect_os) |
|
log "Detected OS: $OS" |
|
echo |
|
|
|
# ============================= |
|
# CORE INSTALLERS (No Dependencies) |
|
# ============================= |
|
|
|
install_build_tools() { |
|
log "Installing GCC & Make (Build Essentials)..." |
|
if [[ "$OS" == "Ubuntu" || "$OS" == "WSL" ]]; then |
|
sudo apt-get update && sudo apt-get install -y build-essential |
|
elif [[ "$OS" == "macOS" ]]; then |
|
xcode-select --install || warn "Xcode tools might already be installed" |
|
else |
|
warn "Manual installation required for this OS." |
|
fi |
|
} |
|
|
|
install_nvm() { |
|
log "Installing NVM..." |
|
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash |
|
|
|
local SOURCERC="$HOME/.sourcerc" |
|
if [[ -f "$SOURCERC" ]] && ! grep -q "NVM_DIR" "$SOURCERC"; then |
|
log "Injecting NVM configuration into .sourcerc..." |
|
cat << 'EOF' >> "$SOURCERC" |
|
|
|
# ========================================== |
|
# NVM (Node Version Manager) |
|
# ========================================== |
|
export NVM_DIR="$HOME/.nvm" |
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" |
|
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" |
|
EOF |
|
fi |
|
info "NOTE: After installing NVM, restart your terminal and run: 'nvm install --lts'" |
|
} |
|
|
|
install_python() { |
|
log "Installing Python 3..." |
|
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/python_installer.sh)" |
|
cleanup_python_tmp |
|
} |
|
|
|
install_go() { |
|
log "Installing Go..." |
|
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/go_installer.sh)" |
|
|
|
local PATHRC_FILE="$HOME/.pathrc" |
|
if [[ -f "$PATHRC_FILE" ]] && ! grep -q "GO_BIN" "$PATHRC_FILE"; then |
|
log "Injecting Go bin path into .pathrc..." |
|
awk -v block=' |
|
# === GO PATH === |
|
GO_BIN="$HOME/go/bin" |
|
if [ -d "$GO_BIN" ]; then |
|
case ":$NEW_PATH:" in |
|
*":$GO_BIN:"*) ;; |
|
*) NEW_PATH="$NEW_PATH:$GO_BIN" ;; |
|
esac |
|
else |
|
if [ "$SUPPRESS_WARNINGS" -ne 1 ]; then |
|
echo "Warning: $GO_BIN does not exist." >&2 |
|
fi |
|
fi |
|
' '/# === Export final PATH ===/ {print block} 1' "$PATHRC_FILE" > "${PATHRC_FILE}.tmp" && mv "${PATHRC_FILE}.tmp" "$PATHRC_FILE" |
|
fi |
|
} |
|
|
|
install_sdkman() { |
|
log "Installing SDKMAN..." |
|
export SDKMAN_DIR="$HOME/.sdkman" |
|
curl -fsSL https://get.sdkman.io | bash |
|
|
|
# Check if we are on macOS and running a Bash version older than 4 |
|
if [[ "$OS" == "macOS" && "${BASH_VERSINFO[0]}" -lt 4 ]]; then |
|
info "macOS with Bash < 4 detected. Routing through Zsh (nonomatch) to bypass strict globbing..." |
|
zsh -o nonomatch <(curl -fsSL "https://get.sdkman.io") |
|
else |
|
curl -fsSL https://get.sdkman.io | bash |
|
fi |
|
|
|
local SOURCERC="$HOME/.sourcerc" |
|
if [[ -f "$SOURCERC" ]] && ! grep -q "sdkman-init.sh" "$SOURCERC"; then |
|
log "Injecting SDKMAN configuration into .sourcerc..." |
|
cat << 'EOF' >> "$SOURCERC" |
|
|
|
# ========================================== |
|
# SDKMAN (Java/Kotlin Version Manager) |
|
# ========================================== |
|
export SDKMAN_DIR="$HOME/.sdkman" |
|
[[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]] && source "$SDKMAN_DIR/bin/sdkman-init.sh" |
|
EOF |
|
fi |
|
} |
|
|
|
install_dotnet() { |
|
log "Installing .NET..." |
|
curl -fsSL https://dot.net/v1/dotnet-install.sh | bash |
|
|
|
local PATHRC_FILE="$HOME/.pathrc" |
|
if [[ -f "$PATHRC_FILE" ]] && ! grep -q "DOTNET_TOOLS" "$PATHRC_FILE"; then |
|
log "Injecting .NET Tools path into .pathrc..." |
|
awk -v block=' |
|
# === .NET TOOLS PATH === |
|
DOTNET_TOOLS="$HOME/.dotnet/tools" |
|
if [ -d "$DOTNET_TOOLS" ]; then |
|
case ":$NEW_PATH:" in |
|
*":$DOTNET_TOOLS:"*) ;; |
|
*) NEW_PATH="$NEW_PATH:$DOTNET_TOOLS" ;; |
|
esac |
|
else |
|
if [ "$SUPPRESS_WARNINGS" -ne 1 ]; then |
|
echo "Warning: $DOTNET_TOOLS does not exist." >&2 |
|
fi |
|
fi |
|
' '/# === Export final PATH ===/ {print block} 1' "$PATHRC_FILE" > "${PATHRC_FILE}.tmp" && mv "${PATHRC_FILE}.tmp" "$PATHRC_FILE" |
|
fi |
|
info "NOTE: .NET is installed to ~/.dotnet by default." |
|
} |
|
|
|
# ============================= |
|
# STANDALONE CLI TOOLS |
|
# ============================= |
|
|
|
install_docker() { |
|
log "Checking Docker installation..." |
|
|
|
if ! command -v docker >/dev/null; then |
|
if [[ "$OS" == "macOS" ]]; then |
|
if command -v brew >/dev/null; then |
|
log "Detected Homebrew. Installing Docker Desktop..." |
|
brew install --cask docker |
|
info "Docker Desktop installed. Launch it from Applications to finish setup." |
|
return 0 |
|
else |
|
err "Homebrew not found. Install Docker Desktop manually." |
|
return 1 |
|
fi |
|
else |
|
log "Installing Docker Engine..." |
|
curl -fsSL https://get.docker.com | sh |
|
fi |
|
else |
|
log "Docker is already installed." |
|
fi |
|
|
|
if [[ "$OS" != "macOS" ]]; then |
|
log "Verifying Docker permissions..." |
|
if ! groups "$USER" | grep -q docker; then |
|
log "Adding $USER to 'docker' group..." |
|
sudo usermod -aG docker "$USER" |
|
fi |
|
if [[ -S /var/run/docker.sock ]]; then |
|
log "Applying immediate permission fix to Docker socket..." |
|
sudo chmod 666 /var/run/docker.sock |
|
fi |
|
fi |
|
} |
|
|
|
install_aws() { |
|
log "Installing AWS CLI..." |
|
if command -v aws >/dev/null; then warn "AWS CLI already installed"; return; fi |
|
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" |
|
unzip -q awscliv2.zip && sudo ./aws/install && rm -rf aws awscliv2.zip |
|
} |
|
|
|
install_ngrok() { |
|
log "Installing Ngrok..." |
|
if command -v ngrok >/dev/null; then warn "Ngrok is already installed"; return; fi |
|
|
|
if [[ "$OS" == "macOS" ]]; then |
|
if command -v brew >/dev/null; then |
|
brew install ngrok/ngrok/ngrok |
|
else |
|
warn "Homebrew not found." |
|
fi |
|
else |
|
log "Installing Ngrok (Linux/WSL)..." |
|
curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \ |
|
| sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null \ |
|
&& echo "deb https://ngrok-agent.s3.amazonaws.com bookworm main" \ |
|
| sudo tee /etc/apt/sources.list.d/ngrok.list \ |
|
&& sudo apt update \ |
|
&& sudo apt install -y ngrok |
|
fi |
|
info "Run 'ngrok config add-authtoken <YOUR_TOKEN>' to authenticate." |
|
} |
|
|
|
install_gh() { |
|
log "Installing GitHub CLI (gh)..." |
|
if command -v gh >/dev/null; then warn "GitHub CLI already installed"; return; fi |
|
|
|
if [[ "$OS" == "macOS" ]]; then |
|
if command -v brew >/dev/null; then |
|
brew install gh |
|
else |
|
warn "Homebrew not found." |
|
fi |
|
else |
|
log "Setting up GitHub apt repository..." |
|
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y) |
|
sudo mkdir -p -m 755 /etc/apt/keyrings |
|
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ |
|
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ |
|
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ |
|
&& sudo apt update \ |
|
&& sudo apt install gh -y |
|
fi |
|
info "GitHub CLI installed. Run 'gh auth login' to authenticate." |
|
} |
|
|
|
install_glab() { |
|
log "Installing GitLab CLI (glab)..." |
|
if command -v glab >/dev/null; then warn "GitLab CLI already installed"; return; fi |
|
|
|
if [[ "$OS" == "macOS" ]]; then |
|
if command -v brew >/dev/null; then brew install glab; fi |
|
else |
|
curl -sL https://gitlab.com/gitlab-org/cli/-/raw/main/scripts/install.sh | sudo bash |
|
fi |
|
info "GitLab CLI installed. Run 'glab auth login' to authenticate." |
|
} |
|
|
|
install_infisical() { |
|
log "Installing Infisical CLI..." |
|
if command -v infisical >/dev/null; then warn "Infisical CLI is already installed"; return; fi |
|
|
|
if [[ "$OS" == "macOS" ]]; then |
|
if command -v brew >/dev/null; then |
|
brew install infisical/get-cli/infisical |
|
else |
|
warn "Homebrew not found. Install Infisical manually." |
|
fi |
|
elif [[ "$OS" == "Ubuntu" || "$OS" == "WSL" ]]; then |
|
curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | sudo -E bash |
|
sudo apt-get update && sudo apt-get install -y infisical |
|
elif command -v yum >/dev/null; then |
|
curl -1sLf 'https://artifacts-cli.infisical.com/setup.rpm.sh' | sudo -E bash |
|
sudo yum install -y infisical |
|
elif command -v apk >/dev/null; then |
|
apk add --no-cache bash sudo wget |
|
wget -qO- 'https://artifacts-cli.infisical.com/setup.apk.sh' | sudo sh |
|
apk update && sudo apk add infisical |
|
else |
|
warn "Unsupported OS for automatic Infisical installation. Please install manually." |
|
fi |
|
info "Infisical CLI installed. Run 'infisical login' to authenticate." |
|
} |
|
|
|
install_claude() { log "Installing Claude Code (Native)..."; curl -fsSL https://claude.ai/install.sh | bash; } |
|
|
|
install_opencode() { |
|
log "Installing OpenCode (opencode.ai)..." |
|
curl -fsSL https://opencode.ai/install | bash |
|
|
|
local PATHRC_FILE="$HOME/.pathrc" |
|
if [[ -f "$PATHRC_FILE" ]] && ! grep -q "OPENCODE_PATH" "$PATHRC_FILE"; then |
|
log "Injecting OpenCode configuration into .pathrc..." |
|
awk -v block=' |
|
# === OPENCODE PATH === |
|
OPENCODE_PATH="$HOME/.opencode/bin" |
|
if [ -d "$OPENCODE_PATH" ]; then |
|
case ":$NEW_PATH:" in |
|
*":$OPENCODE_PATH:"*) ;; # Already in PATH |
|
*) NEW_PATH="$NEW_PATH:$OPENCODE_PATH" ;; |
|
esac |
|
else |
|
if [ "$SUPPRESS_WARNINGS" -ne 1 ]; then |
|
echo "Warning: $OPENCODE_PATH does not exist." >&2 |
|
fi |
|
fi |
|
' '/# === Export final PATH ===/ {print block} 1' "$PATHRC_FILE" > "${PATHRC_FILE}.tmp" && mv "${PATHRC_FILE}.tmp" "$PATHRC_FILE" |
|
fi |
|
|
|
info "OpenCode installed!" |
|
info "Run 'source ~/.zshrc' to apply path changes, then run 'opencode' to start the agent." |
|
} |
|
|
|
# ============================= |
|
# DEPENDENT TOOLS (Require Node or GH) |
|
# ============================= |
|
|
|
install_appwrite() { require_node || return 0; log "Installing Appwrite CLI..."; npm install -g appwrite-cli; } |
|
install_vercel() { require_node || return 0; log "Installing Vercel CLI..."; npm install -g vercel; } |
|
install_trigger() { require_node || return 0; log "Installing Trigger.dev CLI..."; npm install -g @trigger.dev/cli; } |
|
install_gemini() { require_node || return 0; log "Installing Gemini CLI..."; npm install -g @google/gemini-cli; } |
|
|
|
install_copilot_cli() { |
|
log "Installing GitHub Copilot CLI (gh extension)..." |
|
|
|
# 1. Ensure 'gh' is installed |
|
if ! command -v gh >/dev/null; then |
|
warn "'gh' (GitHub CLI) is not found but is required for Copilot." |
|
install_gh |
|
fi |
|
|
|
# 2. Check if user is logged in (optional check, but good for UX) |
|
if ! gh auth status >/dev/null 2>&1; then |
|
warn "You are not logged into GitHub CLI." |
|
info "Please run 'gh auth login' in a separate terminal, then retry this installation." |
|
read -p "Press Enter if you have logged in, or Ctrl+C to exit..." |
|
fi |
|
|
|
# 3. Install Extension |
|
log "Installing gh-copilot extension..." |
|
gh extension install github/gh-copilot --force |
|
|
|
info "Copilot CLI installed! Usage:" |
|
info " • gh copilot suggest \"how to undo last commit\"" |
|
info " • gh copilot explain \"ls -lah\"" |
|
} |
|
|
|
# ============================= |
|
# MENU |
|
# ============================= |
|
while true; do |
|
echo "===========================================" |
|
echo " Developer Environment Installer" |
|
echo "===========================================" |
|
echo " --- Core & Runtimes (No Dependencies) ---" |
|
echo " 1) GCC & Make (Build Essentials)" |
|
echo " 2) NVM (Node Version Manager)" |
|
echo " 3) Python 3" |
|
echo " 4) Go" |
|
echo " 5) SDKMAN (Java/Kotlin Version Manager)" |
|
echo " 6) .NET" |
|
echo " --- Standalone Tools (No Dependencies) ---" |
|
echo " 7) Docker (Install or Fix Permissions)" |
|
echo " 8) AWS CLI" |
|
echo " 9) Ngrok" |
|
echo " 10) GitHub CLI (gh)" |
|
echo " 11) GitLab CLI (glab)" |
|
echo " 12) Infisical CLI" |
|
echo " 13) Claude Code CLI (Native)" |
|
echo " 14) OpenCode (opencode.ai)" |
|
echo " --- Dependent Tools (Require Node or GH) ---" |
|
echo " 15) Appwrite CLI (Requires Node)" |
|
echo " 16) Vercel CLI (Requires Node)" |
|
echo " 17) Trigger.dev CLI (Requires Node)" |
|
echo " 18) Google Gemini CLI (Requires Node 20+)" |
|
echo " 19) GitHub Copilot CLI (Requires gh)" |
|
echo "===========================================" |
|
echo " 20) Quit" |
|
echo "===========================================" |
|
echo "👉 You can enter multiple values (e.g., 1 2 10 19)" |
|
read -p "Select: " input |
|
echo |
|
|
|
input="${input//,/ }" |
|
|
|
for choice in $input; do |
|
case "$choice" in |
|
1) install_build_tools || warn "Build tools failed" ;; |
|
2) install_nvm || warn "NVM install failed" ;; |
|
3) install_python || warn "Python install failed" ;; |
|
4) install_go || warn "Go install failed" ;; |
|
5) install_sdkman || warn "SDKMAN install failed" ;; |
|
6) install_dotnet || warn ".NET install failed" ;; |
|
7) install_docker || warn "Docker install failed" ;; |
|
8) install_aws || warn "AWS install failed" ;; |
|
9) install_ngrok || warn "Ngrok install failed" ;; |
|
10) install_gh || warn "GitHub CLI install failed" ;; |
|
11) install_glab || warn "GitLab CLI install failed" ;; |
|
12) install_infisical || warn "Infisical CLI install failed" ;; |
|
13) install_claude || warn "Claude Code install failed" ;; |
|
14) install_opencode || warn "OpenCode install failed" ;; |
|
15) install_appwrite || warn "Appwrite install failed" ;; |
|
16) install_vercel || warn "Vercel install failed" ;; |
|
17) install_trigger || warn "Trigger.dev install failed" ;; |
|
18) install_gemini || warn "Gemini CLI install failed" ;; |
|
19) install_copilot_cli || warn "Copilot CLI install failed" ;; |
|
20) log "Goodbye!"; exit 0 ;; |
|
*) warn "Skipping invalid option: $choice" ;; |
|
esac |
|
echo |
|
done |
|
|
|
read -p "Install more tools? (y/n): " again |
|
[[ "$again" =~ ^[Yy]$ ]] || break |
|
done |
|
|
|
log "All requested installations completed!" |