830 lines
33 KiB
Bash
830 lines
33 KiB
Bash
#!/bin/bash
|
||
|
||
#===============================================================================
|
||
#
|
||
# Claude Code Installer for Linux
|
||
# A colorized, interactive installer with full system detection
|
||
#
|
||
# Features:
|
||
# - Detects Linux distribution and version
|
||
# - Checks/removes existing Node.js and npm installations
|
||
# - Installs Node.js 20 LTS
|
||
# - Installs Claude Code
|
||
# - Configures permissions interactively
|
||
#
|
||
#===============================================================================
|
||
|
||
# Exit on error
|
||
set -e
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Color Definitions
|
||
#-------------------------------------------------------------------------------
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
MAGENTA='\033[0;35m'
|
||
CYAN='\033[0;36m'
|
||
WHITE='\033[1;37m'
|
||
BOLD='\033[1m'
|
||
DIM='\033[2m'
|
||
RESET='\033[0m'
|
||
|
||
# Status icons
|
||
CHECK="✓"
|
||
CROSS="✗"
|
||
ARROW="➜"
|
||
GEAR="⚙"
|
||
PACKAGE="📦"
|
||
ROCKET="🚀"
|
||
SHIELD="🛡"
|
||
WARNING="⚠"
|
||
INFO="ℹ"
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Helper Functions
|
||
#-------------------------------------------------------------------------------
|
||
|
||
print_banner() {
|
||
clear
|
||
echo -e "${CYAN}"
|
||
echo "╔═══════════════════════════════════════════════════════════════════════════╗"
|
||
echo "║ ║"
|
||
echo "║ ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗ ║"
|
||
echo "║ ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝ ║"
|
||
echo "║ ██║ ██║ ███████║██║ ██║██║ ██║█████╗ ║"
|
||
echo "║ ██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ║"
|
||
echo "║ ╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗ ║"
|
||
echo "║ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ║"
|
||
echo "║ ║"
|
||
echo "║ ${WHITE}C O D E I N S T A L L E R${CYAN} ║"
|
||
echo "║ ║"
|
||
echo "╚═══════════════════════════════════════════════════════════════════════════╝"
|
||
echo -e "${RESET}"
|
||
echo ""
|
||
}
|
||
|
||
print_section() {
|
||
echo ""
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
||
echo -e "${BOLD}${WHITE} $1${RESET}"
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
||
echo ""
|
||
}
|
||
|
||
print_step() {
|
||
echo -e "${CYAN}${ARROW}${RESET} ${WHITE}$1${RESET}"
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}${CHECK}${RESET} ${GREEN}$1${RESET}"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}${CROSS}${RESET} ${RED}$1${RESET}"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}${WARNING}${RESET} ${YELLOW}$1${RESET}"
|
||
}
|
||
|
||
print_info() {
|
||
echo -e "${BLUE}${INFO}${RESET} ${DIM}$1${RESET}"
|
||
}
|
||
|
||
print_package() {
|
||
echo -e "${MAGENTA}${PACKAGE}${RESET} $1"
|
||
}
|
||
|
||
spinner() {
|
||
local pid=$1
|
||
local delay=0.1
|
||
local spinstr='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
||
while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
|
||
local temp=${spinstr#?}
|
||
printf " ${CYAN}[%c]${RESET} " "$spinstr"
|
||
local spinstr=$temp${spinstr%"$temp"}
|
||
sleep $delay
|
||
printf "\b\b\b\b\b\b"
|
||
done
|
||
printf " \b\b\b\b"
|
||
}
|
||
|
||
confirm() {
|
||
local prompt="$1"
|
||
local default="${2:-y}"
|
||
|
||
if [[ "$default" == "y" ]]; then
|
||
prompt_text="${prompt} [${GREEN}Y${RESET}/${RED}n${RESET}]"
|
||
else
|
||
prompt_text="${prompt} [${GREEN}y${RESET}/${RED}N${RESET}]"
|
||
fi
|
||
|
||
echo -ne "${YELLOW}?${RESET} ${prompt_text}: "
|
||
read -r response
|
||
|
||
if [[ -z "$response" ]]; then
|
||
response="$default"
|
||
fi
|
||
|
||
[[ "$response" =~ ^[Yy]$ ]]
|
||
}
|
||
|
||
wait_for_key() {
|
||
echo ""
|
||
echo -ne "${DIM}Press any key to continue...${RESET}"
|
||
read -n 1 -s
|
||
echo ""
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# System Detection Functions
|
||
#-------------------------------------------------------------------------------
|
||
|
||
detect_distro() {
|
||
print_section "${GEAR} System Detection"
|
||
|
||
print_step "Identifying your Linux distribution..."
|
||
sleep 0.5
|
||
|
||
# Initialize variables
|
||
DISTRO="Unknown"
|
||
DISTRO_VERSION="Unknown"
|
||
DISTRO_CODENAME="Unknown"
|
||
DISTRO_FAMILY="Unknown"
|
||
PACKAGE_MANAGER="Unknown"
|
||
|
||
# Try to detect from /etc/os-release (most modern distros)
|
||
if [[ -f /etc/os-release ]]; then
|
||
source /etc/os-release
|
||
DISTRO="${NAME:-Unknown}"
|
||
DISTRO_VERSION="${VERSION_ID:-Unknown}"
|
||
DISTRO_CODENAME="${VERSION_CODENAME:-Unknown}"
|
||
|
||
# Determine distro family and package manager
|
||
case "${ID:-}" in
|
||
ubuntu|debian|linuxmint|pop|elementary|zorin|kali)
|
||
DISTRO_FAMILY="Debian"
|
||
PACKAGE_MANAGER="apt"
|
||
;;
|
||
fedora|rhel|centos|rocky|almalinux|oracle)
|
||
DISTRO_FAMILY="RedHat"
|
||
if command -v dnf &> /dev/null; then
|
||
PACKAGE_MANAGER="dnf"
|
||
else
|
||
PACKAGE_MANAGER="yum"
|
||
fi
|
||
;;
|
||
arch|manjaro|endeavouros|garuda)
|
||
DISTRO_FAMILY="Arch"
|
||
PACKAGE_MANAGER="pacman"
|
||
;;
|
||
opensuse*|sles)
|
||
DISTRO_FAMILY="SUSE"
|
||
PACKAGE_MANAGER="zypper"
|
||
;;
|
||
alpine)
|
||
DISTRO_FAMILY="Alpine"
|
||
PACKAGE_MANAGER="apk"
|
||
;;
|
||
*)
|
||
DISTRO_FAMILY="Unknown"
|
||
;;
|
||
esac
|
||
# Fallback detection methods
|
||
elif [[ -f /etc/lsb-release ]]; then
|
||
source /etc/lsb-release
|
||
DISTRO="${DISTRIB_ID:-Unknown}"
|
||
DISTRO_VERSION="${DISTRIB_RELEASE:-Unknown}"
|
||
DISTRO_CODENAME="${DISTRIB_CODENAME:-Unknown}"
|
||
DISTRO_FAMILY="Debian"
|
||
PACKAGE_MANAGER="apt"
|
||
elif [[ -f /etc/redhat-release ]]; then
|
||
DISTRO=$(cat /etc/redhat-release | cut -d' ' -f1)
|
||
DISTRO_VERSION=$(cat /etc/redhat-release | grep -oE '[0-9]+\.[0-9]+' | head -1)
|
||
DISTRO_FAMILY="RedHat"
|
||
PACKAGE_MANAGER="yum"
|
||
fi
|
||
|
||
# Get kernel and architecture info
|
||
KERNEL_VERSION=$(uname -r)
|
||
ARCH=$(uname -m)
|
||
|
||
# Display detected information
|
||
echo ""
|
||
echo -e " ${WHITE}┌─────────────────────────────────────────────────────────┐${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${BOLD}System Information${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}├─────────────────────────────────────────────────────────┤${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}Distribution:${RESET} ${GREEN}$DISTRO${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}Version:${RESET} ${GREEN}$DISTRO_VERSION${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}Codename:${RESET} ${GREEN}$DISTRO_CODENAME${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}Family:${RESET} ${GREEN}$DISTRO_FAMILY${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}Package Manager:${RESET} ${GREEN}$PACKAGE_MANAGER${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}Kernel:${RESET} ${GREEN}$KERNEL_VERSION${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}Architecture:${RESET} ${GREEN}$ARCH${RESET}"
|
||
echo -e " ${WHITE}└─────────────────────────────────────────────────────────┘${RESET}"
|
||
echo ""
|
||
|
||
# Check if distribution is supported
|
||
if [[ "$PACKAGE_MANAGER" == "Unknown" ]]; then
|
||
print_error "Unsupported Linux distribution detected!"
|
||
print_info "This installer supports: Debian/Ubuntu, Fedora/RHEL/CentOS, Arch, openSUSE, Alpine"
|
||
exit 1
|
||
fi
|
||
|
||
print_success "Distribution detected successfully!"
|
||
sleep 1
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Node.js Detection and Installation
|
||
#-------------------------------------------------------------------------------
|
||
|
||
check_existing_node() {
|
||
print_section "${PACKAGE} Node.js & npm Detection"
|
||
|
||
print_step "Checking for existing Node.js installation..."
|
||
sleep 0.5
|
||
|
||
NODE_INSTALLED=false
|
||
NPM_INSTALLED=false
|
||
NODE_VERSION=""
|
||
NPM_VERSION=""
|
||
NEEDS_UPGRADE=false
|
||
|
||
# Check Node.js
|
||
if command -v node &> /dev/null; then
|
||
NODE_INSTALLED=true
|
||
NODE_VERSION=$(node --version 2>/dev/null || echo "error")
|
||
if [[ "$NODE_VERSION" == "error" ]]; then
|
||
NODE_VERSION="Broken installation"
|
||
NEEDS_UPGRADE=true
|
||
fi
|
||
fi
|
||
|
||
# Check npm
|
||
if command -v npm &> /dev/null; then
|
||
NPM_INSTALLED=true
|
||
NPM_VERSION=$(npm --version 2>/dev/null || echo "error")
|
||
if [[ "$NPM_VERSION" == "error" ]]; then
|
||
NPM_VERSION="Broken installation"
|
||
NEEDS_UPGRADE=true
|
||
fi
|
||
fi
|
||
|
||
# Display current status
|
||
echo ""
|
||
echo -e " ${WHITE}┌─────────────────────────────────────────────────────────┐${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${BOLD}Current Installation Status${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}├─────────────────────────────────────────────────────────┤${RESET}"
|
||
|
||
if [[ "$NODE_INSTALLED" == true ]]; then
|
||
# Check if version is sufficient (need v18+)
|
||
NODE_MAJOR=$(echo "$NODE_VERSION" | sed 's/v//' | cut -d'.' -f1)
|
||
if [[ "$NODE_MAJOR" =~ ^[0-9]+$ ]] && [[ "$NODE_MAJOR" -ge 18 ]]; then
|
||
echo -e " ${WHITE}│${RESET} ${GREEN}${CHECK}${RESET} Node.js: ${GREEN}$NODE_VERSION${RESET} (Compatible)"
|
||
else
|
||
echo -e " ${WHITE}│${RESET} ${YELLOW}${WARNING}${RESET} Node.js: ${YELLOW}$NODE_VERSION${RESET} (Needs upgrade to v18+)"
|
||
NEEDS_UPGRADE=true
|
||
fi
|
||
else
|
||
echo -e " ${WHITE}│${RESET} ${RED}${CROSS}${RESET} Node.js: ${RED}Not installed${RESET}"
|
||
NEEDS_UPGRADE=true
|
||
fi
|
||
|
||
if [[ "$NPM_INSTALLED" == true ]]; then
|
||
echo -e " ${WHITE}│${RESET} ${GREEN}${CHECK}${RESET} npm: ${GREEN}v$NPM_VERSION${RESET}"
|
||
else
|
||
echo -e " ${WHITE}│${RESET} ${RED}${CROSS}${RESET} npm: ${RED}Not installed${RESET}"
|
||
fi
|
||
|
||
echo -e " ${WHITE}└─────────────────────────────────────────────────────────┘${RESET}"
|
||
echo ""
|
||
|
||
# Determine action needed
|
||
if [[ "$NODE_INSTALLED" == true ]] && [[ "$NEEDS_UPGRADE" == true ]]; then
|
||
print_warning "Your Node.js version is incompatible with Claude Code."
|
||
print_info "Claude Code requires Node.js version 18.0.0 or higher."
|
||
echo ""
|
||
|
||
if confirm "Would you like to remove the existing installation and install Node.js 20 LTS?"; then
|
||
remove_existing_node
|
||
install_node
|
||
else
|
||
print_error "Cannot proceed without Node.js 18+. Exiting."
|
||
exit 1
|
||
fi
|
||
elif [[ "$NODE_INSTALLED" == false ]]; then
|
||
print_info "Node.js is not installed on your system."
|
||
echo ""
|
||
|
||
if confirm "Would you like to install Node.js 20 LTS?"; then
|
||
install_node
|
||
else
|
||
print_error "Cannot proceed without Node.js. Exiting."
|
||
exit 1
|
||
fi
|
||
else
|
||
print_success "Node.js installation is compatible!"
|
||
echo ""
|
||
|
||
if confirm "Would you like to reinstall Node.js anyway (fresh installation)?" "n"; then
|
||
remove_existing_node
|
||
install_node
|
||
fi
|
||
fi
|
||
}
|
||
|
||
remove_existing_node() {
|
||
print_section "${GEAR} Removing Existing Node.js Installation"
|
||
|
||
print_step "Removing Node.js, npm, and related packages..."
|
||
echo ""
|
||
|
||
case "$PACKAGE_MANAGER" in
|
||
apt)
|
||
print_info "Removing Debian/Ubuntu packages..."
|
||
|
||
# List of packages to remove
|
||
PACKAGES_TO_REMOVE="nodejs npm libnode-dev libnode72 nodejs-doc"
|
||
|
||
for pkg in $PACKAGES_TO_REMOVE; do
|
||
if dpkg -l | grep -q "^ii $pkg"; then
|
||
echo -ne " ${YELLOW}→${RESET} Removing ${CYAN}$pkg${RESET}..."
|
||
sudo apt remove -y $pkg > /dev/null 2>&1 || true
|
||
echo -e " ${GREEN}done${RESET}"
|
||
fi
|
||
done
|
||
|
||
# Remove NodeSource list if exists
|
||
if [[ -f /etc/apt/sources.list.d/nodesource.list ]]; then
|
||
echo -ne " ${YELLOW}→${RESET} Removing old NodeSource repository..."
|
||
sudo rm -f /etc/apt/sources.list.d/nodesource.list
|
||
echo -e " ${GREEN}done${RESET}"
|
||
fi
|
||
|
||
# Clean up
|
||
echo -ne " ${YELLOW}→${RESET} Running autoremove..."
|
||
sudo apt autoremove -y > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
|
||
echo -ne " ${YELLOW}→${RESET} Cleaning apt cache..."
|
||
sudo apt clean > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
;;
|
||
|
||
dnf|yum)
|
||
print_info "Removing Fedora/RHEL packages..."
|
||
echo -ne " ${YELLOW}→${RESET} Removing nodejs and npm..."
|
||
sudo $PACKAGE_MANAGER remove -y nodejs npm > /dev/null 2>&1 || true
|
||
echo -e " ${GREEN}done${RESET}"
|
||
;;
|
||
|
||
pacman)
|
||
print_info "Removing Arch packages..."
|
||
echo -ne " ${YELLOW}→${RESET} Removing nodejs and npm..."
|
||
sudo pacman -Rns --noconfirm nodejs npm > /dev/null 2>&1 || true
|
||
echo -e " ${GREEN}done${RESET}"
|
||
;;
|
||
|
||
zypper)
|
||
print_info "Removing openSUSE packages..."
|
||
echo -ne " ${YELLOW}→${RESET} Removing nodejs and npm..."
|
||
sudo zypper remove -y nodejs npm > /dev/null 2>&1 || true
|
||
echo -e " ${GREEN}done${RESET}"
|
||
;;
|
||
|
||
apk)
|
||
print_info "Removing Alpine packages..."
|
||
echo -ne " ${YELLOW}→${RESET} Removing nodejs and npm..."
|
||
sudo apk del nodejs npm > /dev/null 2>&1 || true
|
||
echo -e " ${GREEN}done${RESET}"
|
||
;;
|
||
esac
|
||
|
||
# Remove global npm packages location
|
||
if [[ -d /usr/local/lib/node_modules ]]; then
|
||
echo -ne " ${YELLOW}→${RESET} Cleaning global node_modules..."
|
||
sudo rm -rf /usr/local/lib/node_modules
|
||
echo -e " ${GREEN}done${RESET}"
|
||
fi
|
||
|
||
# Remove npm cache
|
||
if [[ -d ~/.npm ]]; then
|
||
echo -ne " ${YELLOW}→${RESET} Cleaning npm cache..."
|
||
rm -rf ~/.npm
|
||
echo -e " ${GREEN}done${RESET}"
|
||
fi
|
||
|
||
# Remove nvm if exists
|
||
if [[ -d ~/.nvm ]]; then
|
||
if confirm " Found nvm installation. Remove it too?" "n"; then
|
||
rm -rf ~/.nvm
|
||
# Remove nvm lines from shell configs
|
||
sed -i '/NVM_DIR/d' ~/.bashrc 2>/dev/null || true
|
||
sed -i '/nvm.sh/d' ~/.bashrc 2>/dev/null || true
|
||
sed -i '/NVM_DIR/d' ~/.zshrc 2>/dev/null || true
|
||
sed -i '/nvm.sh/d' ~/.zshrc 2>/dev/null || true
|
||
print_success "nvm removed"
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
print_success "Existing Node.js installation removed!"
|
||
sleep 1
|
||
}
|
||
|
||
install_node() {
|
||
print_section "${PACKAGE} Installing Node.js 20 LTS"
|
||
|
||
print_step "Preparing to install Node.js 20 LTS..."
|
||
echo ""
|
||
|
||
case "$PACKAGE_MANAGER" in
|
||
apt)
|
||
print_info "Using NodeSource repository for Debian/Ubuntu..."
|
||
echo ""
|
||
|
||
# Install prerequisites
|
||
echo -ne " ${YELLOW}→${RESET} Installing prerequisites..."
|
||
sudo apt update > /dev/null 2>&1
|
||
sudo apt install -y ca-certificates curl gnupg > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
|
||
# Add NodeSource repository
|
||
echo -ne " ${YELLOW}→${RESET} Adding NodeSource repository..."
|
||
curl -fsSL https://deb.nodesource.com/setup_20.x 2>/dev/null | sudo -E bash - > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
|
||
# Install Node.js
|
||
echo -e " ${YELLOW}→${RESET} Installing Node.js 20 LTS..."
|
||
sudo apt install -y nodejs 2>&1 | while read line; do
|
||
echo -e " ${DIM}$line${RESET}"
|
||
done
|
||
;;
|
||
|
||
dnf)
|
||
print_info "Using NodeSource repository for Fedora..."
|
||
echo ""
|
||
|
||
echo -ne " ${YELLOW}→${RESET} Adding NodeSource repository..."
|
||
curl -fsSL https://rpm.nodesource.com/setup_20.x 2>/dev/null | sudo bash - > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
|
||
echo -e " ${YELLOW}→${RESET} Installing Node.js 20 LTS..."
|
||
sudo dnf install -y nodejs 2>&1 | while read line; do
|
||
echo -e " ${DIM}$line${RESET}"
|
||
done
|
||
;;
|
||
|
||
yum)
|
||
print_info "Using NodeSource repository for RHEL/CentOS..."
|
||
echo ""
|
||
|
||
echo -ne " ${YELLOW}→${RESET} Adding NodeSource repository..."
|
||
curl -fsSL https://rpm.nodesource.com/setup_20.x 2>/dev/null | sudo bash - > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
|
||
echo -e " ${YELLOW}→${RESET} Installing Node.js 20 LTS..."
|
||
sudo yum install -y nodejs 2>&1 | while read line; do
|
||
echo -e " ${DIM}$line${RESET}"
|
||
done
|
||
;;
|
||
|
||
pacman)
|
||
print_info "Installing from Arch repositories..."
|
||
echo ""
|
||
|
||
echo -ne " ${YELLOW}→${RESET} Updating package database..."
|
||
sudo pacman -Sy > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
|
||
echo -e " ${YELLOW}→${RESET} Installing Node.js..."
|
||
sudo pacman -S --noconfirm nodejs npm 2>&1 | while read line; do
|
||
echo -e " ${DIM}$line${RESET}"
|
||
done
|
||
;;
|
||
|
||
zypper)
|
||
print_info "Installing from openSUSE repositories..."
|
||
echo ""
|
||
|
||
echo -ne " ${YELLOW}→${RESET} Refreshing repositories..."
|
||
sudo zypper refresh > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
|
||
echo -e " ${YELLOW}→${RESET} Installing Node.js..."
|
||
sudo zypper install -y nodejs20 npm20 2>&1 | while read line; do
|
||
echo -e " ${DIM}$line${RESET}"
|
||
done
|
||
;;
|
||
|
||
apk)
|
||
print_info "Installing from Alpine repositories..."
|
||
echo ""
|
||
|
||
echo -ne " ${YELLOW}→${RESET} Updating package index..."
|
||
sudo apk update > /dev/null 2>&1
|
||
echo -e " ${GREEN}done${RESET}"
|
||
|
||
echo -e " ${YELLOW}→${RESET} Installing Node.js..."
|
||
sudo apk add nodejs npm 2>&1 | while read line; do
|
||
echo -e " ${DIM}$line${RESET}"
|
||
done
|
||
;;
|
||
esac
|
||
|
||
echo ""
|
||
|
||
# Rehash command paths
|
||
hash -r 2>/dev/null || true
|
||
|
||
print_success "Node.js installation completed!"
|
||
sleep 1
|
||
}
|
||
|
||
verify_node_installation() {
|
||
print_section "${CHECK} Verifying Installation"
|
||
|
||
print_step "Checking Node.js and npm versions..."
|
||
echo ""
|
||
sleep 0.5
|
||
|
||
# Rehash to ensure we get fresh paths
|
||
hash -r 2>/dev/null || true
|
||
|
||
# Verify Node.js
|
||
if command -v node &> /dev/null; then
|
||
NEW_NODE_VERSION=$(node --version)
|
||
NODE_MAJOR=$(echo "$NEW_NODE_VERSION" | sed 's/v//' | cut -d'.' -f1)
|
||
|
||
if [[ "$NODE_MAJOR" -ge 18 ]]; then
|
||
echo -e " ${GREEN}${CHECK}${RESET} Node.js: ${GREEN}${BOLD}$NEW_NODE_VERSION${RESET} ${GREEN}(Compatible!)${RESET}"
|
||
NODE_OK=true
|
||
else
|
||
echo -e " ${RED}${CROSS}${RESET} Node.js: ${RED}$NEW_NODE_VERSION (Too old - need v18+)${RESET}"
|
||
NODE_OK=false
|
||
fi
|
||
else
|
||
echo -e " ${RED}${CROSS}${RESET} Node.js: ${RED}Not found!${RESET}"
|
||
NODE_OK=false
|
||
fi
|
||
|
||
# Verify npm
|
||
if command -v npm &> /dev/null; then
|
||
NEW_NPM_VERSION=$(npm --version)
|
||
echo -e " ${GREEN}${CHECK}${RESET} npm: ${GREEN}${BOLD}v$NEW_NPM_VERSION${RESET}"
|
||
NPM_OK=true
|
||
else
|
||
echo -e " ${RED}${CROSS}${RESET} npm: ${RED}Not found!${RESET}"
|
||
NPM_OK=false
|
||
fi
|
||
|
||
# Show paths
|
||
echo ""
|
||
echo -e " ${DIM}Node path: $(which node 2>/dev/null || echo 'Not found')${RESET}"
|
||
echo -e " ${DIM}npm path: $(which npm 2>/dev/null || echo 'Not found')${RESET}"
|
||
|
||
echo ""
|
||
|
||
if [[ "$NODE_OK" == true ]] && [[ "$NPM_OK" == true ]]; then
|
||
print_success "All requirements verified successfully!"
|
||
return 0
|
||
else
|
||
print_error "Verification failed! Please check the installation."
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Claude Code Installation
|
||
#-------------------------------------------------------------------------------
|
||
|
||
install_claude_code() {
|
||
print_section "${ROCKET} Installing Claude Code"
|
||
|
||
print_step "Installing Claude Code via npm..."
|
||
echo ""
|
||
|
||
# Create npm global directory in user space to avoid permission issues
|
||
if [[ ! -d ~/.npm-global ]]; then
|
||
mkdir -p ~/.npm-global
|
||
npm config set prefix '~/.npm-global'
|
||
|
||
# Add to PATH if not already there
|
||
if ! grep -q 'npm-global/bin' ~/.bashrc 2>/dev/null; then
|
||
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
|
||
fi
|
||
if [[ -f ~/.zshrc ]] && ! grep -q 'npm-global/bin' ~/.zshrc 2>/dev/null; then
|
||
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc
|
||
fi
|
||
|
||
# Update current session
|
||
export PATH=~/.npm-global/bin:$PATH
|
||
fi
|
||
|
||
echo -e " ${YELLOW}→${RESET} Downloading and installing Claude Code..."
|
||
echo ""
|
||
|
||
# Install with visible progress
|
||
npm install -g @anthropic-ai/claude-code 2>&1 | while IFS= read -r line; do
|
||
if [[ "$line" == *"added"* ]] || [[ "$line" == *"packages"* ]]; then
|
||
echo -e " ${GREEN}$line${RESET}"
|
||
elif [[ "$line" == *"WARN"* ]]; then
|
||
echo -e " ${YELLOW}$line${RESET}"
|
||
elif [[ "$line" == *"ERR"* ]] || [[ "$line" == *"error"* ]]; then
|
||
echo -e " ${RED}$line${RESET}"
|
||
else
|
||
echo -e " ${DIM}$line${RESET}"
|
||
fi
|
||
done
|
||
|
||
# Rehash
|
||
hash -r 2>/dev/null || true
|
||
|
||
echo ""
|
||
|
||
# Verify installation
|
||
if command -v claude &> /dev/null; then
|
||
CLAUDE_PATH=$(which claude)
|
||
print_success "Claude Code installed successfully!"
|
||
echo -e " ${DIM}Location: $CLAUDE_PATH${RESET}"
|
||
else
|
||
# Try with full path
|
||
if [[ -f ~/.npm-global/bin/claude ]]; then
|
||
print_success "Claude Code installed successfully!"
|
||
echo -e " ${DIM}Location: ~/.npm-global/bin/claude${RESET}"
|
||
print_warning "You may need to restart your terminal or run: source ~/.bashrc"
|
||
else
|
||
print_error "Claude Code installation may have failed!"
|
||
print_info "Try running: npm install -g @anthropic-ai/claude-code"
|
||
return 1
|
||
fi
|
||
fi
|
||
|
||
sleep 1
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Permission Configuration
|
||
#-------------------------------------------------------------------------------
|
||
|
||
configure_permissions() {
|
||
print_section "${SHIELD} Permission Configuration"
|
||
|
||
echo -e " Claude Code can run with different permission levels:"
|
||
echo ""
|
||
echo -e " ${WHITE}┌─────────────────────────────────────────────────────────────────────┐${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}1.${RESET} ${BOLD}Normal Mode${RESET} (Recommended for most users) ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${DIM}Claude will ask permission before file edits and commands${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${GREEN}✓ Safe ✓ Interactive ✓ Full control${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}2.${RESET} ${BOLD}Auto-Accept Edits${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${DIM}Automatically accept file edits, ask for commands${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${YELLOW}⚡ Faster editing ✓ Command safety${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}3.${RESET} ${BOLD}Full Auto Mode${RESET} (--dangerously-skip-permissions) ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${DIM}Skip all permission prompts (use with caution!)${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${RED}⚠ No confirmations ⚠ Full system access${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${CYAN}4.${RESET} ${BOLD}Don't start Claude${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${DIM}Exit installer without starting Claude${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}│${RESET} ${WHITE}│${RESET}"
|
||
echo -e " ${WHITE}└─────────────────────────────────────────────────────────────────────┘${RESET}"
|
||
echo ""
|
||
|
||
while true; do
|
||
echo -ne "${YELLOW}?${RESET} Select permission mode [${GREEN}1${RESET}/${CYAN}2${RESET}/${RED}3${RESET}/${WHITE}4${RESET}]: "
|
||
read -r choice
|
||
|
||
case "$choice" in
|
||
1)
|
||
PERMISSION_MODE="normal"
|
||
CLAUDE_CMD="claude"
|
||
print_success "Selected: Normal Mode"
|
||
break
|
||
;;
|
||
2)
|
||
PERMISSION_MODE="auto-edit"
|
||
CLAUDE_CMD="claude --auto-accept-edits"
|
||
print_success "Selected: Auto-Accept Edits"
|
||
break
|
||
;;
|
||
3)
|
||
PERMISSION_MODE="full-auto"
|
||
echo ""
|
||
print_warning "⚠️ WARNING: Full Auto Mode gives Claude unrestricted access!"
|
||
echo -e " ${RED}This mode will:${RESET}"
|
||
echo -e " ${RED} • Execute commands without confirmation${RESET}"
|
||
echo -e " ${RED} • Modify files without asking${RESET}"
|
||
echo -e " ${RED} • Have full access to your system${RESET}"
|
||
echo ""
|
||
|
||
if confirm "Are you sure you want to use Full Auto Mode?" "n"; then
|
||
CLAUDE_CMD="claude --dangerously-skip-permissions"
|
||
print_success "Selected: Full Auto Mode"
|
||
break
|
||
else
|
||
echo ""
|
||
print_info "Please select a different mode."
|
||
echo ""
|
||
fi
|
||
;;
|
||
4)
|
||
PERMISSION_MODE="exit"
|
||
CLAUDE_CMD=""
|
||
print_info "Installation complete. You can start Claude anytime with: claude"
|
||
break
|
||
;;
|
||
*)
|
||
print_error "Invalid choice. Please enter 1, 2, 3, or 4."
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Launch Claude Code
|
||
#-------------------------------------------------------------------------------
|
||
|
||
launch_claude() {
|
||
if [[ "$PERMISSION_MODE" == "exit" ]]; then
|
||
print_section "${CHECK} Installation Complete!"
|
||
echo -e " ${GREEN}Claude Code has been successfully installed!${RESET}"
|
||
echo ""
|
||
echo -e " ${WHITE}To start Claude Code, run:${RESET}"
|
||
echo ""
|
||
echo -e " ${CYAN}claude${RESET} ${DIM}# Normal mode${RESET}"
|
||
echo -e " ${CYAN}claude --auto-accept-edits${RESET} ${DIM}# Auto-accept file edits${RESET}"
|
||
echo -e " ${CYAN}claude --dangerously-skip-permissions${RESET} ${DIM}# Full auto mode${RESET}"
|
||
echo ""
|
||
echo -e " ${DIM}If 'claude' is not found, restart your terminal or run:${RESET}"
|
||
echo -e " ${CYAN}source ~/.bashrc${RESET}"
|
||
echo ""
|
||
print_success "Thank you for installing Claude Code!"
|
||
return
|
||
fi
|
||
|
||
print_section "${ROCKET} Launching Claude Code"
|
||
|
||
echo -e " ${WHITE}Starting Claude Code with: ${CYAN}$CLAUDE_CMD${RESET}"
|
||
echo ""
|
||
echo -e " ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
||
echo ""
|
||
|
||
sleep 1
|
||
|
||
# Ensure PATH includes npm-global
|
||
export PATH=~/.npm-global/bin:$PATH
|
||
|
||
# Launch Claude
|
||
exec $CLAUDE_CMD
|
||
}
|
||
|
||
#-------------------------------------------------------------------------------
|
||
# Main Script Execution
|
||
#-------------------------------------------------------------------------------
|
||
|
||
main() {
|
||
# Check if running as root (not recommended but handle gracefully)
|
||
if [[ $EUID -eq 0 ]]; then
|
||
print_warning "Running as root. This is not recommended for Claude Code."
|
||
print_info "Consider running as a regular user with sudo access."
|
||
echo ""
|
||
if ! confirm "Continue anyway?"; then
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# Display banner
|
||
print_banner
|
||
|
||
echo -e " ${WHITE}Welcome to the Claude Code Installer!${RESET}"
|
||
echo ""
|
||
echo -e " ${DIM}This script will:${RESET}"
|
||
echo -e " ${DIM} 1. Detect your Linux distribution${RESET}"
|
||
echo -e " ${DIM} 2. Check/install Node.js 20 LTS${RESET}"
|
||
echo -e " ${DIM} 3. Install Claude Code${RESET}"
|
||
echo -e " ${DIM} 4. Configure permissions${RESET}"
|
||
echo ""
|
||
|
||
if ! confirm "Ready to begin installation?"; then
|
||
echo ""
|
||
print_info "Installation cancelled. Goodbye!"
|
||
exit 0
|
||
fi
|
||
|
||
# Run installation steps
|
||
detect_distro
|
||
check_existing_node
|
||
verify_node_installation || exit 1
|
||
install_claude_code || exit 1
|
||
configure_permissions
|
||
launch_claude
|
||
}
|
||
|
||
# Run main function
|
||
main "$@"
|