diff --git a/scripts/installation/install-qwen-with-source.sh b/scripts/installation/install-qwen-with-source.sh index 399cbc1f8..15164b391 100755 --- a/scripts/installation/install-qwen-with-source.sh +++ b/scripts/installation/install-qwen-with-source.sh @@ -1,194 +1,371 @@ #!/bin/bash -# Script to install Node.js and Qwen Code with source information -# This script handles the installation process and sets the installation source +# Qwen Code Installation Script +# This script installs Node.js (via NVM) and Qwen Code CLI +# Supports Linux and macOS # # Usage: install-qwen-with-source.sh --source [github|npm|internal|local-build] # install-qwen-with-source.sh -s [github|npm|internal|local-build] -# Check if running with sh (which doesn't support pipefail) -if [[ -z "${BASH_VERSION}" ]]; then - # Re-execute with bash - exec bash "${0}" "${@}" +# Re-execute with bash if running with sh or other shells +# Skip re-exec if already in bash and avoid double-fork in git hooks +if [[ -z "${BASH_VERSION}" ]] && [[ -z "${__QWEN_INSTALL_REEXEC:-}" ]]; then + # Check if we're in a git hook environment + if [[ "${0}" == *".git/hooks/"* ]] || [[ -n "${GIT_DIR:-}" ]]; then + export __QWEN_IN_GIT_HOOK=1 + fi + + # Try to find bash + if command -v bash >/dev/null 2>&1; then + export __QWEN_INSTALL_REEXEC=1 + # Use exec only if not in git hook to avoid nested shell issues + if [[ -n "${__QWEN_IN_GIT_HOOK:-}" ]]; then + exec bash "${0}" "${@}" + else + exec bash "${0}" "${@}" + fi + else + echo "Error: This script requires bash. Please install bash first." + exit 1 + fi fi +set -eo pipefail -# Disable pagers to prevent interactive prompts -export GIT_PAGER=cat -export PAGER=cat +# ============================================ +# Color definitions +# ============================================ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color -# Enable pipefail to catch errors in pipelines -set -o pipefail - -# Function to display usage -usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " -s, --source SOURCE Specify the installation source (e.g., github, npm, internal)" - echo " -h, --help Show this help message" - echo "" - exit 1 +# ============================================ +# Log functions +# ============================================ +log_info() { + echo -e "${BLUE}ℹ️ $1${NC}" } +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +# ============================================ +# Utility functions +# ============================================ +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +get_shell_profile() { + local current_shell + current_shell=$(basename "${SHELL}") + case "${current_shell}" in + bash) + echo "${HOME}/.bashrc" + ;; + zsh) + echo "${HOME}/.zshrc" + ;; + fish) + echo "${HOME}/.config/fish/config.fish" + ;; + *) + echo "${HOME}/.profile" + ;; + esac +} + +# ============================================ # Parse command line arguments +# ============================================ SOURCE="unknown" while [[ $# -gt 0 ]]; do case $1 in -s|--source) if [[ -z "$2" ]] || [[ "$2" == -* ]]; then - echo "Error: --source requires a value" - usage + log_error "--source requires a value" + exit 1 fi SOURCE="$2" shift 2 ;; -h|--help) - usage + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -s, --source SOURCE Specify the installation source (e.g., github, npm, internal)" + echo " -h, --help Show this help message" + echo "" + exit 0 ;; *) - usage + log_error "Unknown option: $1" + exit 1 ;; esac done -echo "===========================================" -echo "Qwen Code Installation Script with Source Tracking" -echo "===========================================" +# ============================================ +# Print header +# ============================================ +echo "==========================================" +echo " Qwen Code Installation Script" +echo "==========================================" +echo "" +log_info "System: $(uname -s) $(uname -r)" || true +log_info "Shell: $(basename "${SHELL}")" +echo "" -# Function to check if a command exists -command_exists() { - command -v "$1" >/dev/null 2>&1 -} - -# Global variable for download command -DOWNLOAD_CMD="curl -f -s -S -L" - -# Function to ensure curl or wget is available -ensure_curl_or_wget() { +# ============================================ +# Ensure download tool is available +# ============================================ +ensure_download_tool() { if command_exists curl; then - DOWNLOAD_CMD="curl -f -s -S -L" + DOWNLOAD_CMD="curl" + DOWNLOAD_ARGS="-fsSL" return 0 fi if command_exists wget; then - echo "curl not found, using wget for downloads." - DOWNLOAD_CMD="wget -q -O -" + DOWNLOAD_CMD="wget" + DOWNLOAD_ARGS="-qO -" return 0 fi - echo "Neither curl nor wget found. Attempting to install..." + log_error "Neither curl nor wget found" + log_info "Please install curl or wget manually:" + echo " - macOS: brew install curl" + echo " - Ubuntu/Debian: sudo apt-get install curl" + echo " - CentOS/RHEL: sudo yum install curl" + exit 1 +} - # Check if we're root or have sudo - CURRENT_UID=$(id -u) || true - if [[ "${CURRENT_UID}" -eq 0 ]]; then - # Running as root, no sudo needed - SUDO_CMD="" - elif command_exists sudo; then - if sudo -n true 2>/dev/null || true; then - # Have sudo without password - SUDO_CMD="sudo" - fi - fi - - if [[ -z "${SUDO_CMD}" ]] && [[ "${CURRENT_UID}" -ne 0 ]]; then - echo "Error: Cannot install curl - sudo is not available and not running as root." - echo "Please install curl or wget manually and run this script again." - exit 1 - fi - - # Try to install curl based on OS - if command_exists apt-get; then - echo "Installing curl via apt-get..." - ${SUDO_CMD} apt-get update && ${SUDO_CMD} apt-get install -y curl || true - elif command_exists dnf; then - echo "Installing curl via dnf..." - ${SUDO_CMD} dnf install -y curl || true - elif command_exists pacman; then - echo "Installing curl via pacman..." - ${SUDO_CMD} pacman -Syu --noconfirm curl || true - elif command_exists zypper; then - echo "Installing curl via zypper..." - ${SUDO_CMD} zypper install -y curl || true - elif command_exists yum; then - echo "Installing curl via yum..." - ${SUDO_CMD} yum install -y curl || true - elif command_exists brew; then - echo "Installing curl via Homebrew..." - ${SUDO_CMD} brew install curl || true - elif command_exists /opt/homebrew/bin/brew; then - echo "Installing curl via Homebrew (ARM)..." - ${SUDO_CMD} /opt/homebrew/bin/brew install curl || true - else - echo "Error: Cannot install curl - no supported package manager found." - echo "Please install curl or wget manually and run this script again." - exit 1 - fi - - # Verify installation - if command_exists curl; then - echo "✓ curl installed successfully" - return 0 - else - echo "✗ Failed to install curl" - exit 1 +# ============================================ +# Clean npm configuration conflicts +# ============================================ +clean_npmrc_conflict() { + local npmrc="${HOME}/.npmrc" + if [[ -f "${npmrc}" ]]; then + log_info "Cleaning npmrc conflicts..." + grep -Ev '^(prefix|globalconfig) *= *' "${npmrc}" > "${npmrc}.tmp" || true + mv -f "${npmrc}.tmp" "${npmrc}" || true fi } -# Function to check if sudo is available -check_sudo_available() { - if command_exists sudo; then - # Check if sudo actually works (non-root user may have sudo but not configured) - if sudo -n true 2>/dev/null; then +# ============================================ +# Install NVM +# ============================================ +install_nvm() { + local NVM_DIR="${NVM_DIR:-${HOME}/.nvm}" + local NVM_VERSION="${NVM_VERSION:-v0.40.3}" + + if [[ -s "${NVM_DIR}/nvm.sh" ]]; then + log_info "NVM is already installed at ${NVM_DIR}" + return 0 + fi + + log_info "Installing NVM ${NVM_VERSION}..." + + # Download and install NVM from Aliyun OSS + # Use temporary file instead of pipe to avoid potential subshell issues + local NVM_INSTALL_TEMP + NVM_INSTALL_TEMP=$(mktemp) + if "${DOWNLOAD_CMD}" "${DOWNLOAD_ARGS}" "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh" > "${NVM_INSTALL_TEMP}"; then + # Run the script in current shell environment + # shellcheck source=/dev/null + . "${NVM_INSTALL_TEMP}" + rm -f "${NVM_INSTALL_TEMP}" + log_success "NVM installed successfully" + else + rm -f "${NVM_INSTALL_TEMP}" + log_error "Failed to install NVM" + log_info "Please install NVM manually: https://github.com/nvm-sh/nvm#install--update-script" + exit 1 + fi + + # Configure shell profile + local PROFILE_FILE + PROFILE_FILE=$(get_shell_profile) + + # Check if profile file is writable + if [[ -f "${PROFILE_FILE}" ]] && [[ ! -w "${PROFILE_FILE}" ]]; then + log_warning "Cannot write to ${PROFILE_FILE} (permission denied)" + log_info "Skipping shell profile configuration" + log_info "You may need to manually add NVM configuration to your shell profile" + elif ! grep -q 'NVM_DIR' "${PROFILE_FILE}" 2>/dev/null; then + # shellcheck disable=SC2016 + # The following echo statements intentionally use single quotes to write literal strings + { + echo "" + echo "# NVM configuration (added by Qwen Code installer)" + echo "export NVM_DIR=\"\$HOME/.nvm\"" + echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' + echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' + } >> "${PROFILE_FILE}" 2>/dev/null || { + log_warning "Failed to write to ${PROFILE_FILE}" + log_info "Skipping shell profile configuration" return 0 - else - echo "Warning: sudo is installed but requires password." - return 1 - fi + } + log_info "Added NVM config to ${PROFILE_FILE}" fi - # No sudo found - check if we're running as root - CHECK_UID=$(id -u) || true - if [[ "${CHECK_UID}" -eq 0 ]]; then - return 0 - fi + # Load NVM for current session + export NVM_DIR="${NVM_DIR}" + # shellcheck source=/dev/null + [[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" - echo "Error: sudo is not available and you are not running as root." - echo "" - echo "This script requires either:" - echo " 1. sudo access (run with a user in sudoers group)" - echo " 2. root access (run as root)" - echo "" - echo "Please run this script with proper permissions or install packages manually." - return 1 + log_success "NVM configured successfully" + return 0 } -# Function to fix npm global directory permissions -fix_npm_permissions() { - echo "Fixing npm global directory permissions..." +# ============================================ +# Install Node.js via NVM +# ============================================ +install_nodejs_with_nvm() { + local NODE_VERSION="${NODE_VERSION:-20}" + local NVM_DIR="${NVM_DIR:-${HOME}/.nvm}" - # Get the actual npm global directory - NPM_GLOBAL_DIR=$(npm config get prefix 2>/dev/null) + # Ensure NVM is loaded + export NVM_DIR="${NVM_DIR}" + # shellcheck source=/dev/null + [[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" + + if ! command_exists nvm; then + log_error "NVM not loaded properly" + return 1 + fi + + # Set Node.js mirror source for faster downloads in China + export NVM_NODEJS_ORG_MIRROR="https://npmmirror.com/mirrors/node" + + # Install Node.js + log_info "Installing Node.js v${NODE_VERSION}..." + if nvm install "${NODE_VERSION}"; then + nvm alias default "${NODE_VERSION}" || true + nvm use default || true + log_success "Node.js v${NODE_VERSION} installed successfully" + + # Verify installation + log_info "Node.js version: $(node -v)" || true + log_info "npm version: $(npm -v)" || true + + return 0 + else + log_error "Failed to install Node.js" + return 1 + fi +} + +# ============================================ +# Check Node.js version +# ============================================ +check_node_version() { + if ! command_exists node; then + return 1 + fi + + local current_version + current_version=$(node -v | sed 's/v//') + local major_version + major_version=$(echo "${current_version}" | cut -d. -f1) + + if [[ "${major_version}" -ge 20 ]]; then + log_success "Node.js v${current_version} is already installed (>= 20)" + return 0 + else + log_warning "Node.js v${current_version} is installed but version < 20" + return 1 + fi +} + +# ============================================ +# Install Node.js +# ============================================ +install_nodejs() { + local platform + platform=$(uname -s) + + case "${platform}" in + Linux|Darwin) + log_info "Installing Node.js on ${platform}..." + + # Install NVM + if ! install_nvm; then + log_error "Failed to install NVM" + return 1 + fi + + # Load NVM + export NVM_DIR="${HOME}/.nvm" + # shellcheck source=/dev/null + [[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" + + # Install Node.js + if ! install_nodejs_with_nvm; then + log_error "Failed to install Node.js" + return 1 + fi + ;; + MINGW*|CYGWIN*|MSYS*) + log_error "Windows platform detected. Please use Windows installer or WSL." + log_info "Visit: https://nodejs.org/en/download/" + exit 1 + ;; + *) + log_error "Unsupported platform: ${platform}" + exit 1 + ;; + esac +} + +# ============================================ +# Check and install Node.js +# ============================================ +check_and_install_nodejs() { + if check_node_version; then + log_info "Using existing Node.js installation" + clean_npmrc_conflict + else + log_warning "Installing or upgrading Node.js..." + install_nodejs + fi +} + +# ============================================ +# Fix npm permissions (without using sudo) +# ============================================ +fix_npm_permissions() { + log_info "Checking npm permissions..." + + local NPM_GLOBAL_DIR + NPM_GLOBAL_DIR=$(npm config get prefix 2>/dev/null) || true if [[ -z "${NPM_GLOBAL_DIR}" ]] || [[ "${NPM_GLOBAL_DIR}" == *"error"* ]]; then - # Fallback to default if npm config fails NPM_GLOBAL_DIR="${HOME}/.npm-global" - echo "Warning: Could not determine npm prefix, using fallback: ${NPM_GLOBAL_DIR}" + npm config set prefix "${NPM_GLOBAL_DIR}" + log_info "Set npm prefix to user directory: ${NPM_GLOBAL_DIR}" + return 0 fi # SAFETY CHECK: Never modify system directories # This prevents catastrophic failures like breaking sudo setuid binaries case "${NPM_GLOBAL_DIR}" in /|/usr|/usr/local|/bin|/sbin|/lib|/lib64|/opt|/snap|/var|/etc) - echo "Warning: npm prefix is a system directory (${NPM_GLOBAL_DIR})." - echo "Skipping permission fix to avoid breaking system binaries." - echo "" - echo "This is likely a system-wide npm installation." - echo "Consider using a user-owned npm prefix instead:" - echo " npm config set prefix ~/.npm-global" - echo "" - echo "Alternatively, you can manually fix permissions for your user directory:" - echo " mkdir -p ~/.npm-global" - echo " npm config set prefix ~/.npm-global" + log_warning "npm prefix is a system directory (${NPM_GLOBAL_DIR})." + log_info "Using user directory instead to avoid breaking system binaries." + NPM_GLOBAL_DIR="${HOME}/.npm-global" + npm config set prefix "${NPM_GLOBAL_DIR}" + log_success "npm prefix set to: ${NPM_GLOBAL_DIR}" return 0 ;; *) @@ -196,600 +373,179 @@ fix_npm_permissions() { ;; esac - # 1. Change ownership of the entire npm global directory to current user - # Using only user ownership without specifying a group for cross-platform compatibility - sudo chown -R "$(whoami)" "${NPM_GLOBAL_DIR}" 2>/dev/null || true - - # 2. Fix directory permissions (ensure user has full read/write/execute permissions) - chmod -R u+rwX "${NPM_GLOBAL_DIR}" 2>/dev/null || true - - # 3. Specifically fix parent directory permissions (to prevent mkdir failures) - chmod u+rwx "${NPM_GLOBAL_DIR}" "${NPM_GLOBAL_DIR}/lib" "${NPM_GLOBAL_DIR}/lib/node_modules" 2>/dev/null || true -} - -# Function to check and install Node.js -install_nodejs() { - if command_exists node; then - NODE_VERSION=$(node --version) - # Extract major version number (remove 'v' prefix and get first number) - NODE_MAJOR_VERSION=$(echo "${NODE_VERSION}" | sed 's/v//' | cut -d'.' -f1) || true - - # Check if NODE_MAJOR_VERSION is a valid number - if ! [[ "${NODE_MAJOR_VERSION}" =~ ^[0-9]+$ ]]; then - echo "⚠ Could not parse Node.js version: ${NODE_VERSION}" - echo "Installing Node.js 20+..." - install_nodejs_via_nvm - elif [[ "${NODE_MAJOR_VERSION}" -ge 20 ]]; then - echo "✓ Node.js is already installed: ${NODE_VERSION}" - - # Check npm after confirming Node.js exists - if ! command_exists npm; then - echo "⚠ npm not found, installing npm..." - if install_npm_only; then - echo "✓ npm installation completed" - else - echo "✗ Failed to install npm" - echo "Please install npm manually or reinstall Node.js from: https://nodejs.org/" - exit 1 - fi - else - if NPM_VERSION=$(npm --version 2>/dev/null) && [[ -n "${NPM_VERSION}" ]]; then - echo "✓ npm v${NPM_VERSION} is available" - else - echo "⚠ npm exists but cannot execute, reinstalling..." - if install_npm_only; then - echo "✓ npm installation fixed" - else - echo "✗ Failed to fix npm" - exit 1 - fi - fi - fi - - # Check if npm global directory has permission issues - if ! npm config get prefix >/dev/null 2>&1; then - fix_npm_permissions - fi - - return 0 - else - echo "⚠ Node.js ${NODE_VERSION} is installed, but Qwen Code requires Node.js 20+" - echo "Installing Node.js 20+..." - install_nodejs_via_nvm - fi - else - echo "Installing Node.js 20+..." - install_nodejs_via_nvm - fi -} - -# Function to check if NVM installation is complete -check_nvm_complete() { - export NVM_DIR="${HOME}/.nvm" - - if [[ ! -d "${NVM_DIR}" ]]; then - return 1 + # Check if npm global directory is writable + if [[ -w "${NPM_GLOBAL_DIR}" ]]; then + log_info "npm global directory is writable" + return 0 fi - if [[ ! -s "${NVM_DIR}/nvm.sh" ]]; then - echo "⚠ Incomplete NVM: nvm.sh missing" - return 1 - fi + # If not writable, use user directory + log_warning "npm global directory is not writable: ${NPM_GLOBAL_DIR}" + log_info "Setting npm prefix to user directory..." - # shellcheck source=/dev/null - if ! \. "${NVM_DIR}/nvm.sh" 2>/dev/null; then - echo "⚠ Corrupted NVM: cannot load nvm.sh" - return 1 - fi + NPM_GLOBAL_DIR="${HOME}/.npm-global" + mkdir -p "${NPM_GLOBAL_DIR}" + npm config set prefix "${NPM_GLOBAL_DIR}" - if ! command_exists nvm; then - echo "⚠ Incomplete NVM: nvm command unavailable" - return 1 + log_success "npm prefix set to: ${NPM_GLOBAL_DIR}" + + # Add to PATH in shell profile + local PROFILE_FILE + PROFILE_FILE=$(get_shell_profile) + if ! grep -q '.npm-global/bin' "${PROFILE_FILE}" 2>/dev/null; then + { + echo "" + echo "# NPM global bin (added by Qwen Code installer)" + echo "export PATH=\"\$HOME/.npm-global/bin:\$PATH\"" + } >> "${PROFILE_FILE}" + log_info "Added npm global bin to PATH in ${PROFILE_FILE}" fi return 0 } -# Function to uninstall NVM -uninstall_nvm() { - echo "Uninstalling NVM..." - export NVM_DIR="${HOME}/.nvm" - - if [[ -d "${NVM_DIR}" ]]; then - # Try to remove the directory, check for errors - if ! rm -rf "${NVM_DIR}" 2>/dev/null; then - echo "⚠ Failed to remove NVM directory (permission denied or files in use)" - echo " Attempting with elevated permissions..." - # Try with sudo if available - if command -v sudo >/dev/null 2>&1; then - sudo rm -rf "${NVM_DIR}" 2>/dev/null || true - fi - fi - - # Verify removal - if [[ -d "${NVM_DIR}" ]]; then - echo "⚠ Warning: Could not fully remove NVM directory at ${NVM_DIR}" - echo " Some files may be in use by other processes." - echo " Continuing anyway, but installation may fail..." - else - echo "✓ Removed NVM directory" - fi - fi - - # Clean shell configs - for config in "${HOME}/.bashrc" "${HOME}/.bash_profile" "${HOME}/.zshrc" "${HOME}/.profile"; do - if [[ -f "${config}" ]]; then - # shellcheck disable=SC2312 - cp "${config}" "${config}.bak.$(date +%s)" 2>/dev/null - sed -i.tmp '/NVM_DIR/d; /nvm.sh/d; /bash_completion/d' "${config}" 2>/dev/null || \ - sed -i '' '/NVM_DIR/d; /nvm.sh/d; /bash_completion/d' "${config}" 2>/dev/null - rm -f "${config}.tmp" 2>/dev/null || true - fi - done - - # Unset nvm function to avoid conflicts with reinstallation - unset -f nvm 2>/dev/null || true - - echo "✓ Cleaned NVM configuration" -} - -# Function to install npm only -install_npm_only() { - echo "Installing npm separately..." - - if command_exists curl || command_exists wget; then - echo "Attempting to install npm using: npmjs.com/install.sh" - if ${DOWNLOAD_CMD} https://www.npmjs.com/install.sh | sh; then - NPM_VERSION_TMP=$(npm --version 2>/dev/null) - if command_exists npm && [[ -n "${NPM_VERSION_TMP}" ]]; then - echo "✓ npm v${NPM_VERSION_TMP} installed via direct install script" - return 0 - fi - fi - else - echo "No download tool (curl/wget) available" - fi - - return 1 -} - -# Function to install Node.js via nvm -install_nodejs_via_nvm() { - export NVM_DIR="${HOME}/.nvm" - - # Check glibc version before attempting installation - # Node.js 20+ requires glibc 2.27+ - GLIBC_VERSION=$(ldd --version 2>&1 | head -n1 | grep -oE '[0-9]+\.[0-9]+' | head -1) - - # Handle empty version - if [[ -z "${GLIBC_VERSION}" ]]; then - # Try alternative method - GLIBC_VERSION=$(ldd -v 2>&1 | grep -oP 'Version\s+\K[0-9.]+' | head -1 || echo "0") - fi - - # Ensure GLIBC_VERSION is a clean value (remove any newlines) - GLIBC_VERSION=$(echo "${GLIBC_VERSION}" | tr -d '\n\r' | sed 's/[[:space:]]//g') - - # Extract major and minor version - GLIBC_MAJOR=$(echo "${GLIBC_VERSION}" | cut -d. -f1) - GLIBC_MINOR=$(echo "${GLIBC_VERSION}" | cut -d. -f2) - GLIBC_MAJOR=${GLIBC_MAJOR:-0} - GLIBC_MINOR=${GLIBC_MINOR:-0} - - if [[ "${GLIBC_MAJOR}" -lt 2 ]] || \ - [[ "${GLIBC_MAJOR}" -eq 2 && "${GLIBC_MINOR}" -lt 27 ]]; then - echo "✗ Error: Detected glibc ${GLIBC_VERSION}" - echo "" - echo "Qwen Code requires Node.js 20+, which needs glibc 2.27+." - echo "Your system (CentOS 7 with glibc 2.17) is not compatible." - echo "" - echo "Please upgrade your OS or use Docker." - echo "" - exit 1 - fi - - # Check NVM completeness - if [[ -d "${NVM_DIR}" ]]; then - if ! check_nvm_complete; then - echo "Detected incomplete NVM installation" - uninstall_nvm - # If directory still exists after uninstall (partial removal), try to clean it - if [[ -d "${NVM_DIR}" ]]; then - echo " Cleaning up residual NVM files..." - # Remove everything except we can't delete (probably in use) - find "${NVM_DIR}" -mindepth 1 -delete 2>/dev/null || true - # If still can't remove the directory itself, warn but continue - if [[ -d "${NVM_DIR}" ]]; then - echo " Note: Some NVM files are locked by running processes." - echo " Will attempt to install NVM over existing directory..." - fi - fi - else - echo "✓ NVM already installed" - fi - fi - - # Install NVM if needed (either no dir or partial/corrupted) - if [[ ! -d "${NVM_DIR}" ]] || [[ ! -s "${NVM_DIR}/nvm.sh" ]]; then - echo "Downloading NVM..." - - # Use mktemp for secure temporary file creation - # Remove trailing slash from TMPDIR to avoid double slashes - TEMP_DIR="${TMPDIR:-/tmp}" - TEMP_DIR="${TEMP_DIR%/}" - - # Retry mktemp a few times if it fails - TMP_INSTALL_SCRIPT="" - for _ in 1 2 3; do - TMP_INSTALL_SCRIPT=$(mktemp "${TEMP_DIR}/nvm_install.XXXXXXXXXX.sh" 2>/dev/null) - if [[ -n "${TMP_INSTALL_SCRIPT}" ]] && [[ -f "${TMP_INSTALL_SCRIPT}" ]]; then - break - fi - # Wait a bit before retry - sleep 0.1 - done - - # Fallback if mktemp still fails - if [[ -z "${TMP_INSTALL_SCRIPT}" ]]; then - TMP_INSTALL_SCRIPT="${TEMP_DIR}/nvm_install_$$_$(date +%s%N).sh" - touch "${TMP_INSTALL_SCRIPT}" 2>/dev/null || { - echo "✗ Failed to create temporary file" - exit 1 - } - fi - - # Ensure cleanup on exit - trap 'rm -f "${TMP_INSTALL_SCRIPT}"' EXIT - - if ${DOWNLOAD_CMD} "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh" > "${TMP_INSTALL_SCRIPT}"; then - if bash "${TMP_INSTALL_SCRIPT}"; then - rm -f "${TMP_INSTALL_SCRIPT}" - trap - EXIT - echo "✓ NVM installed" - else - echo "✗ NVM installation failed" - rm -f "${TMP_INSTALL_SCRIPT}" - trap - EXIT - echo "Please install Node.js manually from: https://nodejs.org/" - exit 1 - fi - else - echo "✗ Failed to download NVM" - rm -f "${TMP_INSTALL_SCRIPT}" - trap - EXIT - echo "Please check your internet connection or install Node.js manually from https://nodejs.org/" - exit 1 - fi - fi - - # Load NVM - if [[ -s "${NVM_DIR}/nvm.sh" ]]; then - # shellcheck source=/dev/null - \. "${NVM_DIR}/nvm.sh" - else - echo "✗ NVM installation failed - nvm.sh not found" - echo "Please install Node.js manually from https://nodejs.org/" - exit 1 - fi - - # shellcheck source=/dev/null - [[ -s "${NVM_DIR}/bash_completion" ]] && \. "${NVM_DIR}/bash_completion" - - # Verify NVM loaded - if ! command_exists nvm; then - echo "✗ Failed to load NVM" - echo "Please manually load NVM or install Node.js from https://nodejs.org/" - exit 1 - fi - - # Install Node.js 20 - echo "Installing Node.js 20..." - if nvm install 20 >/dev/null 2>&1; then - nvm use 20 >/dev/null 2>&1 || true - nvm alias default 20 >/dev/null 2>&1 || true - else - echo "✗ Failed to install Node.js 20" - exit 1 - fi - - # Add NVM node to PATH for this script execution - # Find the actual installed Node.js version directory - NVM_NODE_PATH="" - if [[ -d "${NVM_DIR}/versions/node" ]]; then - # Find the v20.x.x directory - NVM_NODE_PATH=$(find "${NVM_DIR}/versions/node" -maxdepth 1 -type d -name 'v20.*' 2>/dev/null | head -1)/bin - fi - - if [[ -n "${NVM_NODE_PATH}" ]] && [[ -d "${NVM_NODE_PATH}" ]]; then - export PATH="${NVM_NODE_PATH}:${PATH}" - fi - - # Verify Node.js - if ! command_exists node; then - echo "✗ Node.js installation verification failed" - exit 1 - fi - - if ! NODE_VERSION=$(node --version 2>/dev/null) || [[ -z "${NODE_VERSION}" ]]; then - echo "✗ Node.js cannot execute properly" - exit 1 - fi - - echo "✓ Node.js ${NODE_VERSION} installed" - - # Check npm separately - if ! command_exists npm; then - echo "⚠ npm not found" - - if install_npm_only; then - echo "✓ npm installation fixed" - else - echo "✗ Failed to install npm" - echo "Please try:" - echo " 1. Run this script again" - echo " 2. Install Node.js from: https://nodejs.org/" - exit 1 - fi - else - if NPM_VERSION=$(npm --version 2>/dev/null) && [[ -n "${NPM_VERSION}" ]]; then - echo "✓ npm v${NPM_VERSION} installed" - else - echo "⚠ npm exists but cannot execute" - - if install_npm_only; then - echo "✓ npm installation fixed" - else - echo "✗ Failed to fix npm" - exit 1 - fi - fi - fi -} - -# Function to check and install Qwen Code +# ============================================ +# Install Qwen Code +# ============================================ install_qwen_code() { # Ensure NVM node is in PATH export NVM_DIR="${HOME}/.nvm" - if [[ -s "${NVM_DIR}/nvm.sh" ]]; then - # shellcheck source=/dev/null - \. "${NVM_DIR}/nvm.sh" 2>/dev/null || true - fi + # shellcheck source=/dev/null + [[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" 2>/dev/null || true - # Also add npm global bin to PATH - NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null || echo "") + # Add npm global bin to PATH + local NPM_GLOBAL_BIN + NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null) || true if [[ -n "${NPM_GLOBAL_BIN}" ]]; then export PATH="${NPM_GLOBAL_BIN}:${PATH}" fi if command_exists qwen; then - QWEN_VERSION=$(qwen --version 2>/dev/null || echo "unknown") - echo "✓ Qwen Code is already installed: ${QWEN_VERSION}" - echo " Upgrading to the latest version..." + local QWEN_VERSION + QWEN_VERSION=$(qwen --version 2>/dev/null) || echo "unknown" + log_success "Qwen Code is already installed: ${QWEN_VERSION}" + log_info "Upgrading to the latest version..." fi - # Check if .npmrc contains incompatible settings for nvm - if [[ -f "${HOME}/.npmrc" ]]; then - if grep -q "prefix\|globalconfig" "${HOME}/.npmrc"; then - echo "⚠ Found incompatible settings in ~/.npmrc for NVM" - echo " Creating temporary backup and removing incompatible settings..." - - # Backup .npmrc file - cp "${HOME}/.npmrc" "${HOME}/.npmrc.backup.before.qwen.install" - - # Create temporary .npmrc without incompatible settings - grep -v -E '^(prefix|globalconfig)' "${HOME}/.npmrc" > "${HOME}/.npmrc.temp.for.qwen.install" - - # Use the temporary .npmrc - mv "${HOME}/.npmrc" "${HOME}/.npmrc.original" - mv "${HOME}/.npmrc.temp.for.qwen.install" "${HOME}/.npmrc" - - # Remember to restore later - RESTORE_NPMRC=true + # Clean npmrc conflicts + clean_npmrc_conflict + + # Fix npm permissions if needed + fix_npm_permissions + + # Configure npm registry for faster downloads in China + npm config set registry https://registry.npmmirror.com + log_info "npm registry set to npmmirror" + + # Install Qwen Code + log_info "Installing Qwen Code..." + if npm install -g @qwen-code/qwen-code@latest; then + log_success "Qwen Code installed successfully!" + + # Verify installation + if command_exists qwen; then + local qwen_version + qwen_version=$(qwen --version 2>/dev/null) || qwen_version="unknown" + log_info "Qwen Code version: ${qwen_version}" fi - fi - - echo " Attempting to install Qwen Code with current user permissions..." - if npm install -g @qwen-code/qwen-code@latest 2>/dev/null; then - echo "✓ Qwen Code installed/upgraded successfully!" else - # Installation failed, likely due to permissions - echo " Installation failed with user permissions, attempting to fix permissions..." - - # Fix npm global directory permissions - fix_npm_permissions - - # Try again after fixing permissions - if npm install -g @qwen-code/qwen-code@latest 2>/dev/null; then - echo "✓ Qwen Code installed/upgraded successfully after permission fix!" - else - # Both attempts failed - echo "✗ Failed to install Qwen Code even after permission fix" - echo " Please check your system permissions or contact support" - # Restore .npmrc if we backed it up - if [[ "${RESTORE_NPMRC}" = true ]]; then - mv "${HOME}/.npmrc" "${HOME}/.npmrc.temp.after.failed.install" - mv "${HOME}/.npmrc.original" "${HOME}/.npmrc" - echo " Restored original ~/.npmrc file" - fi - exit 1 - fi + log_error "Failed to install Qwen Code!" + log_info "Please check your internet connection and try again" + exit 1 fi - # Restore original .npmrc file if we modified it - if [[ "${RESTORE_NPMRC}" = true ]]; then - mv "${HOME}/.npmrc" "${HOME}/.npmrc.temp.after.successful.install" - mv "${HOME}/.npmrc.original" "${HOME}/.npmrc" - echo " Restored original ~/.npmrc file" - fi - - # Create/Update source.json only if source parameter was provided + # Create source.json if source parameter was provided if [[ "${SOURCE}" != "unknown" ]]; then create_source_json - else - echo " (Skipping source.json creation - no source specified)" fi } -# Function to create source.json +# ============================================ +# Create source.json +# ============================================ create_source_json() { - QWEN_DIR="${HOME}/.qwen" + local QWEN_DIR="${HOME}/.qwen" - # Create .qwen directory if it doesn't exist - if [[ ! -d "${QWEN_DIR}" ]]; then - mkdir -p "${QWEN_DIR}" - fi + mkdir -p "${QWEN_DIR}" # Escape special characters in SOURCE for JSON - # Replace backslashes first, then quotes + local ESCAPED_SOURCE ESCAPED_SOURCE=$(printf '%s' "${SOURCE}" | sed 's/\\/\\\\/g; s/"/\\"/g') - # Create source.json file cat > "${QWEN_DIR}/source.json" </dev/null || true - fi - NPM_GLOBAL_BIN="$(npm bin -g 2>/dev/null)" || true + # shellcheck source=/dev/null + [[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" 2>/dev/null || true + local NPM_GLOBAL_BIN + NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null) || true if [[ -n "${NPM_GLOBAL_BIN}" ]]; then export PATH="${NPM_GLOBAL_BIN}:${PATH}" fi # Check if qwen is immediately available if command_exists qwen; then - echo "✓ Qwen Code is ready to use!" + log_success "Qwen Code is ready to use!" echo "" echo "You can now run: qwen" else - echo "⚠ To start using Qwen Code, please run one of the following commands:" + log_warning "To start using Qwen Code, please run:" echo "" - - # Detect user's shell - USER_SHELL=$(basename "${SHELL}") - - if [[ "${USER_SHELL}" = "zsh" ]] && [[ -f "${HOME}/.zshrc" ]]; then - echo " source ~/.zshrc" - elif [[ "${USER_SHELL}" = "bash" ]]; then - if [[ -f "${HOME}/.bash_profile" ]]; then - echo " source ~/.bash_profile" - elif [[ -f "${HOME}/.bashrc" ]]; then - echo " source ~/.bashrc" - fi - else - # Fallback: show all possible options - [[ -f "${HOME}/.zshrc" ]] && echo " source ~/.zshrc" - [[ -f "${HOME}/.bashrc" ]] && echo " source ~/.bashrc" - [[ -f "${HOME}/.bash_profile" ]] && echo " source ~/.bash_profile" - fi - + local PROFILE_FILE + PROFILE_FILE=$(get_shell_profile) + echo " source ${PROFILE_FILE}" echo "" echo "Or simply restart your terminal, then run: qwen" fi - - # Auto-configure PATH in shell config files - NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null || echo "") - NVM_DIR="${HOME}/.nvm" - - # Determine which config file to use - SHELL_CONFIG="" - if [[ -f "${HOME}/.bashrc" ]]; then - SHELL_CONFIG="${HOME}/.bashrc" - elif [[ -f "${HOME}/.bash_profile" ]]; then - SHELL_CONFIG="${HOME}/.bash_profile" - elif [[ -f "${HOME}/.profile" ]]; then - SHELL_CONFIG="${HOME}/.profile" - fi - - if [[ -n "${SHELL_CONFIG}" ]]; then - # Check if already configured - NEEDS_CONFIG=false - if [[ -n "${NPM_GLOBAL_BIN}" ]]; then - if ! grep -q "npm bin -g" "${SHELL_CONFIG}" 2>/dev/null && \ - ! grep -q "${NPM_GLOBAL_BIN}" "${SHELL_CONFIG}" 2>/dev/null; then - NEEDS_CONFIG=true - fi - fi - - if [[ "${NEEDS_CONFIG}" == "true" ]]; then - echo "" - echo "Adding Qwen Code to PATH in ${SHELL_CONFIG}..." - - # Append NVM configuration - if [[ -d "${NVM_DIR}" ]]; then - echo "" >> "${SHELL_CONFIG}" - echo "# NVM configuration (added by Qwen Code installer)" >> "${SHELL_CONFIG}" - echo "export NVM_DIR=\"${NVM_DIR}\"" >> "${SHELL_CONFIG}" - echo "[ -s \"\$NVM_DIR/nvm.sh\" ] && \\. \"\$NVM_DIR/nvm.sh\"" >> "${SHELL_CONFIG}" - echo "[ -s \"\$NVM_DIR/bash_completion\" ] && \\. \"\$NVM_DIR/bash_completion\"" >> "${SHELL_CONFIG}" - fi - - # Append npm global bin to PATH - if [[ -n "${NPM_GLOBAL_BIN}" ]]; then - echo "" >> "${SHELL_CONFIG}" - echo "# NPM global bin (added by Qwen Code installer)" >> "${SHELL_CONFIG}" - echo "export PATH=\"${NPM_GLOBAL_BIN}:\$PATH\"" >> "${SHELL_CONFIG}" - fi - - echo "✓ Configuration added to ${SHELL_CONFIG}" - echo "" - echo "Please run: source ${SHELL_CONFIG}" - fi - fi } # Run main function -main "$@" \ No newline at end of file +main "$@"