From db56ba22cdf48ed1a02c4f19580687a2985994c2 Mon Sep 17 00:00:00 2001 From: DennisYu07 <617072224@qq.com> Date: Wed, 18 Feb 2026 23:52:37 -0800 Subject: [PATCH 1/6] fix chmod error for arch system and add curl and sudo check for shell script --- .../installation/install-qwen-with-source.sh | 159 +++++++++++++++++- 1 file changed, 150 insertions(+), 9 deletions(-) diff --git a/scripts/installation/install-qwen-with-source.sh b/scripts/installation/install-qwen-with-source.sh index 880ee89a7b6..f1e3d06b0ed 100755 --- a/scripts/installation/install-qwen-with-source.sh +++ b/scripts/installation/install-qwen-with-source.sh @@ -54,10 +54,109 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } +# Global variable for download command +DOWNLOAD_CMD="curl -f -s -S -L" +WGET_CMD="wget -q -O -" + +# Function to ensure curl or wget is available +ensure_curl_or_wget() { + if command_exists curl; then + DOWNLOAD_CMD="curl -f -s -S -L" + WGET_CMD="wget -q -O -" + return 0 + fi + + if command_exists wget; then + echo "curl not found, using wget for downloads." + DOWNLOAD_CMD="wget -q -O -" + WGET_CMD="wget -q -O -" + return 0 + fi + + echo "Neither curl nor wget found. Attempting to install..." + + # Check if we're root or have sudo + if [[ "$(id -u)" -eq 0 ]]; then + # Running as root, no sudo needed + SUDO_CMD="" + elif command_exists sudo && sudo -n true 2>/dev/null; then + # Have sudo without password + SUDO_CMD="sudo" + else + 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 + elif command_exists dnf; then + echo "Installing curl via dnf..." + ${SUDO_CMD} dnf install -y curl + elif command_exists pacman; then + echo "Installing curl via pacman..." + ${SUDO_CMD} pacman -Syu --noconfirm curl + elif command_exists zypper; then + echo "Installing curl via zypper..." + ${SUDO_CMD} zypper install -y curl + elif command_exists yum; then + echo "Installing curl via yum..." + ${SUDO_CMD} yum install -y curl + elif command_exists brew; then + echo "Installing curl via Homebrew..." + ${SUDO_CMD} brew install curl + elif command_exists /opt/homebrew/bin/brew; then + echo "Installing curl via Homebrew (ARM)..." + ${SUDO_CMD} /opt/homebrew/bin/brew install curl + 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 + 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 + return 0 + else + echo "Warning: sudo is installed but requires password." + return 1 + fi + fi + + # No sudo found - check if we're running as root + if [[ "$(id -u)" -eq 0 ]]; then + return 0 + fi + + 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 +} + # Function to fix npm global directory permissions fix_npm_permissions() { echo "Fixing npm global directory permissions..." - + # Get the actual npm global directory NPM_GLOBAL_DIR=$(npm config get prefix 2>/dev/null) if [[ -z "${NPM_GLOBAL_DIR}" ]] || [[ "${NPM_GLOBAL_DIR}" == *"error"* ]]; then @@ -65,7 +164,25 @@ fix_npm_permissions() { NPM_GLOBAL_DIR="${HOME}/.npm-global" echo "Warning: Could not determine npm prefix, using fallback: ${NPM_GLOBAL_DIR}" 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" + return 0 + ;; + 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 @@ -207,9 +324,9 @@ uninstall_nvm() { install_npm_only() { echo "Installing npm separately..." - if command_exists curl; then - echo "Attempting to install npm using: curl -qL https://www.npmjs.com/install.sh | sh" - if curl -qL https://www.npmjs.com/install.sh | sh; then + 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" @@ -217,9 +334,9 @@ install_npm_only() { fi fi else - echo "curl command not found, proceeding with alternative methods" + echo "No download tool (curl/wget) available" fi - + return 1 } @@ -280,7 +397,7 @@ install_nodejs_via_nvm() { # Ensure cleanup on exit trap 'rm -f "${TMP_INSTALL_SCRIPT}"' EXIT - if curl -f -s -S -o "${TMP_INSTALL_SCRIPT}" "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh"; then + 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 @@ -481,7 +598,31 @@ EOF main() { # Initialize variables RESTORE_NPMRC=false - + + # Validate HOME variable + if [[ -z "${HOME}" ]]; then + echo "Warning: HOME environment variable is not set." + if [[ "$(id -u)" -eq 0 ]]; then + export HOME="/root" + echo "Using HOME=/root for root user." + else + export HOME=$(eval echo ~$(whoami)) + echo "Using HOME=${HOME}." + fi + fi + + # Validate HOME directory exists + if [[ ! -d "${HOME}" ]]; then + echo "Error: HOME directory (${HOME}) does not exist." + exit 1 + fi + + # Check sudo availability first (basic permission check) + check_sudo_available + + # Ensure curl/wget is available (needed to download NVM) + ensure_curl_or_wget + # Step 1: Check and install Node.js install_nodejs echo "" From 9ac8d9cfb1ca04fbb34e08bb04ceb476b4bdd683 Mon Sep 17 00:00:00 2001 From: DennisYu07 <617072224@qq.com> Date: Thu, 19 Feb 2026 01:16:18 -0800 Subject: [PATCH 2/6] fix issues for debian --- .../installation/install-qwen-with-source.sh | 140 ++++++++++++++++-- 1 file changed, 127 insertions(+), 13 deletions(-) diff --git a/scripts/installation/install-qwen-with-source.sh b/scripts/installation/install-qwen-with-source.sh index f1e3d06b0ed..d59bdc9a8d5 100755 --- a/scripts/installation/install-qwen-with-source.sh +++ b/scripts/installation/install-qwen-with-source.sh @@ -6,6 +6,13 @@ # 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" "$@" +fi + + # Disable pagers to prevent interactive prompts export GIT_PAGER=cat export PAGER=cat @@ -344,6 +351,37 @@ install_npm_only() { 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 @@ -441,13 +479,25 @@ install_nodejs_via_nvm() { # 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 - nvm alias default 20 >/dev/null 2>&1 + 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=$(ls -d "${NVM_DIR}"/versions/node/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" @@ -492,6 +542,19 @@ install_nodejs_via_nvm() { # Function to check and 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 + + # Also add npm global bin to PATH + NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null || echo "") + 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}" @@ -559,16 +622,6 @@ install_qwen_code() { else echo " (Skipping source.json creation - no source specified)" fi - - # Verify installation - if command_exists qwen; then - QWEN_VERSION=$(qwen --version 2>/dev/null || echo "unknown") - echo "✓ Qwen Code is available as 'qwen' command" - echo " Installed version: ${QWEN_VERSION}" - else - echo "⚠ Qwen Code installed but not in PATH" - echo " You may need to restart your terminal" - fi } # Function to create source.json @@ -636,6 +689,17 @@ main() { echo "===========================================" echo "" + # Ensure NVM and npm global bin are in PATH before final check + 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 + NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null || echo "") + 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!" @@ -666,6 +730,56 @@ main() { 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 From bae1ba2d5dddb4482484c953feb826c0721dadce Mon Sep 17 00:00:00 2001 From: DennisYu07 <617072224@qq.com> Date: Thu, 19 Feb 2026 01:33:55 -0800 Subject: [PATCH 3/6] fix warning for shell script --- .../installation/install-qwen-with-source.sh | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/installation/install-qwen-with-source.sh b/scripts/installation/install-qwen-with-source.sh index d59bdc9a8d5..c3d84695516 100755 --- a/scripts/installation/install-qwen-with-source.sh +++ b/scripts/installation/install-qwen-with-source.sh @@ -7,9 +7,9 @@ # 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 +if [[ -z "${BASH_VERSION}" ]]; then # Re-execute with bash - exec bash "$0" "$@" + exec bash "${0}" "${@}" fi @@ -63,20 +63,17 @@ command_exists() { # Global variable for download command DOWNLOAD_CMD="curl -f -s -S -L" -WGET_CMD="wget -q -O -" # Function to ensure curl or wget is available ensure_curl_or_wget() { if command_exists curl; then DOWNLOAD_CMD="curl -f -s -S -L" - WGET_CMD="wget -q -O -" return 0 fi if command_exists wget; then echo "curl not found, using wget for downloads." DOWNLOAD_CMD="wget -q -O -" - WGET_CMD="wget -q -O -" return 0 fi @@ -98,25 +95,25 @@ ensure_curl_or_wget() { # 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 + ${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 + ${SUDO_CMD} dnf install -y curl || true elif command_exists pacman; then echo "Installing curl via pacman..." - ${SUDO_CMD} pacman -Syu --noconfirm curl + ${SUDO_CMD} pacman -Syu --noconfirm curl || true elif command_exists zypper; then echo "Installing curl via zypper..." - ${SUDO_CMD} zypper install -y curl + ${SUDO_CMD} zypper install -y curl || true elif command_exists yum; then echo "Installing curl via yum..." - ${SUDO_CMD} yum install -y curl + ${SUDO_CMD} yum install -y curl || true elif command_exists brew; then echo "Installing curl via Homebrew..." - ${SUDO_CMD} brew install curl + ${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 + ${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." @@ -188,6 +185,9 @@ fix_npm_permissions() { echo " npm config set prefix ~/.npm-global" return 0 ;; + *) + # Safe to proceed with non-system directory + ;; esac # 1. Change ownership of the entire npm global directory to current user From d0acea2185b87784aac4b398eaf1a6ff08f88392 Mon Sep 17 00:00:00 2001 From: DennisYu07 <617072224@qq.com> Date: Thu, 19 Feb 2026 01:47:36 -0800 Subject: [PATCH 4/6] fix warning --- .../installation/install-qwen-with-source.sh | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/scripts/installation/install-qwen-with-source.sh b/scripts/installation/install-qwen-with-source.sh index c3d84695516..399cbc1f86e 100755 --- a/scripts/installation/install-qwen-with-source.sh +++ b/scripts/installation/install-qwen-with-source.sh @@ -80,13 +80,18 @@ ensure_curl_or_wget() { echo "Neither curl nor wget found. Attempting to install..." # Check if we're root or have sudo - if [[ "$(id -u)" -eq 0 ]]; then + CURRENT_UID=$(id -u) || true + if [[ "${CURRENT_UID}" -eq 0 ]]; then # Running as root, no sudo needed SUDO_CMD="" - elif command_exists sudo && sudo -n true 2>/dev/null; then - # Have sudo without password - SUDO_CMD="sudo" - else + 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 @@ -143,7 +148,8 @@ check_sudo_available() { fi # No sudo found - check if we're running as root - if [[ "$(id -u)" -eq 0 ]]; then + CHECK_UID=$(id -u) || true + if [[ "${CHECK_UID}" -eq 0 ]]; then return 0 fi @@ -491,7 +497,7 @@ install_nodejs_via_nvm() { NVM_NODE_PATH="" if [[ -d "${NVM_DIR}/versions/node" ]]; then # Find the v20.x.x directory - NVM_NODE_PATH=$(ls -d "${NVM_DIR}"/versions/node/v20.* 2>/dev/null | head -1)/bin + 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 @@ -655,11 +661,14 @@ main() { # Validate HOME variable if [[ -z "${HOME}" ]]; then echo "Warning: HOME environment variable is not set." - if [[ "$(id -u)" -eq 0 ]]; then + MAIN_UID=$(id -u) || true + if [[ "${MAIN_UID}" -eq 0 ]]; then export HOME="/root" echo "Using HOME=/root for root user." else - export HOME=$(eval echo ~$(whoami)) + CURRENT_USER=$(whoami) || true + DEFAULT_HOME="$(eval echo "~${CURRENT_USER}")" || true + export HOME="${DEFAULT_HOME}" echo "Using HOME=${HOME}." fi fi @@ -695,7 +704,7 @@ main() { # shellcheck source=/dev/null \. "${NVM_DIR}/nvm.sh" 2>/dev/null || true fi - NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null || echo "") + NPM_GLOBAL_BIN="$(npm bin -g 2>/dev/null)" || true if [[ -n "${NPM_GLOBAL_BIN}" ]]; then export PATH="${NPM_GLOBAL_BIN}:${PATH}" fi From ce3fc8f46202eb1cd668a23aa4bf54dd4d59f3b1 Mon Sep 17 00:00:00 2001 From: DennisYu07 <617072224@qq.com> Date: Mon, 23 Feb 2026 19:29:55 -0800 Subject: [PATCH 5/6] refactor installation script --- .../installation/install-qwen-with-source.sh | 1010 +++++++---------- 1 file changed, 383 insertions(+), 627 deletions(-) diff --git a/scripts/installation/install-qwen-with-source.sh b/scripts/installation/install-qwen-with-source.sh index 399cbc1f86e..15164b391be 100755 --- a/scripts/installation/install-qwen-with-source.sh +++ b/scripts/installation/install-qwen-with-source.sh @@ -1,795 +1,551 @@ #!/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 + +# ============================================ +# Color definitions +# ============================================ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# ============================================ +# Log functions +# ============================================ +log_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} -# Disable pagers to prevent interactive prompts -export GIT_PAGER=cat -export PAGER=cat +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} -# Enable pipefail to catch errors in pipelines -set -o pipefail +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} -# 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_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 "===========================================" - -# 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() { +# ============================================ +# Print header +# ============================================ +echo "==========================================" +echo " Qwen Code Installation Script" +echo "==========================================" +echo "" +log_info "System: $(uname -s) $(uname -r)" || true +log_info "Shell: $(basename "${SHELL}")" +echo "" + +# ============================================ +# 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..." - - # 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 + 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 +} - # 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 - return 0 - else - echo "Warning: sudo is installed but requires password." - return 1 - fi - fi +# ============================================ +# Install NVM +# ============================================ +install_nvm() { + local NVM_DIR="${NVM_DIR:-${HOME}/.nvm}" + local NVM_VERSION="${NVM_VERSION:-v0.40.3}" - # No sudo found - check if we're running as root - CHECK_UID=$(id -u) || true - if [[ "${CHECK_UID}" -eq 0 ]]; then + if [[ -s "${NVM_DIR}/nvm.sh" ]]; then + log_info "NVM is already installed at ${NVM_DIR}" return 0 fi - 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 -} - -# Function to fix npm global directory permissions -fix_npm_permissions() { - echo "Fixing npm global directory permissions..." + log_info "Installing NVM ${NVM_VERSION}..." - # Get the actual npm global directory - NPM_GLOBAL_DIR=$(npm config get prefix 2>/dev/null) - 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}" + # 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 - # 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" - return 0 - ;; - *) - # Safe to proceed with non-system directory - ;; - 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 + # 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 "⚠ 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 + } + log_info "Added NVM config to ${PROFILE_FILE}" fi -} -# Function to check if NVM installation is complete -check_nvm_complete() { - export NVM_DIR="${HOME}/.nvm" + # Load NVM for current session + export NVM_DIR="${NVM_DIR}" + # shellcheck source=/dev/null + [[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" - if [[ ! -d "${NVM_DIR}" ]]; then - return 1 - fi + log_success "NVM configured successfully" + return 0 +} - if [[ ! -s "${NVM_DIR}/nvm.sh" ]]; then - echo "⚠ Incomplete NVM: nvm.sh missing" - return 1 - fi +# ============================================ +# Install Node.js via NVM +# ============================================ +install_nodejs_with_nvm() { + local NODE_VERSION="${NODE_VERSION:-20}" + local NVM_DIR="${NVM_DIR:-${HOME}/.nvm}" + # Ensure NVM is loaded + export NVM_DIR="${NVM_DIR}" # shellcheck source=/dev/null - if ! \. "${NVM_DIR}/nvm.sh" 2>/dev/null; then - echo "⚠ Corrupted NVM: cannot load nvm.sh" - return 1 - fi + [[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" if ! command_exists nvm; then - echo "⚠ Incomplete NVM: nvm command unavailable" + log_error "NVM not loaded properly" return 1 fi - return 0 -} + # Set Node.js mirror source for faster downloads in China + export NVM_NODEJS_ORG_MIRROR="https://npmmirror.com/mirrors/node" -# Function to uninstall NVM -uninstall_nvm() { - echo "Uninstalling NVM..." - export NVM_DIR="${HOME}/.nvm" + # 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" - 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 installation + log_info "Node.js version: $(node -v)" || true + log_info "npm version: $(npm -v)" || true - # 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 + return 0 + else + log_error "Failed to install Node.js" + return 1 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 +# ============================================ +# Check Node.js version +# ============================================ +check_node_version() { + if ! command_exists node; then + return 1 + fi - echo "✓ Cleaned NVM configuration" -} + local current_version + current_version=$(node -v | sed 's/v//') + local major_version + major_version=$(echo "${current_version}" | cut -d. -f1) -# 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 + if [[ "${major_version}" -ge 20 ]]; then + log_success "Node.js v${current_version} is already installed (>= 20)" + return 0 else - echo "No download tool (curl/wget) available" + log_warning "Node.js v${current_version} is installed but version < 20" + return 1 fi - - return 1 } -# Function to install Node.js via nvm -install_nodejs_via_nvm() { - export NVM_DIR="${HOME}/.nvm" +# ============================================ +# Install Node.js +# ============================================ +install_nodejs() { + local platform + platform=$(uname -s) - # 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 + case "${platform}" in + Linux|Darwin) + log_info "Installing Node.js on ${platform}..." - # 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 + # Install NVM + if ! install_nvm; then + log_error "Failed to install NVM" + return 1 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 + # Load NVM + export NVM_DIR="${HOME}/.nvm" + # shellcheck source=/dev/null + [[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" - # 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 + # Install Node.js + if ! install_nodejs_with_nvm; then + log_error "Failed to install Node.js" + return 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/" + ;; + MINGW*|CYGWIN*|MSYS*) + log_error "Windows platform detected. Please use Windows installer or WSL." + log_info "Visit: https://nodejs.org/en/download/" 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 + ;; + *) + log_error "Unsupported platform: ${platform}" + exit 1 + ;; + esac +} - # 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 +# ============================================ +# 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 - echo "✗ Failed to install Node.js 20" - exit 1 + log_warning "Installing or upgrading Node.js..." + install_nodejs 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 +# ============================================ +# Fix npm permissions (without using sudo) +# ============================================ +fix_npm_permissions() { + log_info "Checking npm permissions..." - if [[ -n "${NVM_NODE_PATH}" ]] && [[ -d "${NVM_NODE_PATH}" ]]; then - export PATH="${NVM_NODE_PATH}:${PATH}" + 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 + NPM_GLOBAL_DIR="${HOME}/.npm-global" + npm config set prefix "${NPM_GLOBAL_DIR}" + log_info "Set npm prefix to user directory: ${NPM_GLOBAL_DIR}" + return 0 fi - # Verify Node.js - if ! command_exists node; then - echo "✗ Node.js installation verification failed" - exit 1 - 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) + 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 + ;; + *) + # Safe to proceed with non-system directory + ;; + esac - if ! NODE_VERSION=$(node --version 2>/dev/null) || [[ -z "${NODE_VERSION}" ]]; then - echo "✗ Node.js cannot execute properly" - exit 1 + # Check if npm global directory is writable + if [[ -w "${NPM_GLOBAL_DIR}" ]]; then + log_info "npm global directory is writable" + return 0 fi - echo "✓ Node.js ${NODE_VERSION} installed" + # 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..." - # Check npm separately - if ! command_exists npm; then - echo "⚠ npm not found" + NPM_GLOBAL_DIR="${HOME}/.npm-global" + mkdir -p "${NPM_GLOBAL_DIR}" + npm config set prefix "${NPM_GLOBAL_DIR}" - 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" + log_success "npm prefix set to: ${NPM_GLOBAL_DIR}" - if install_npm_only; then - echo "✓ npm installation fixed" - else - echo "✗ Failed to fix npm" - exit 1 - fi - fi + # 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 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 - fi - fi + # Clean npmrc conflicts + clean_npmrc_conflict - 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 permissions if needed + fix_npm_permissions - # Fix npm global directory permissions - 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" - # 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 - fi + # Install Qwen Code + log_info "Installing Qwen Code..." + if npm install -g @qwen-code/qwen-code@latest; then + log_success "Qwen Code installed successfully!" - # 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" + # 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 + else + log_error "Failed to install Qwen Code!" + log_info "Please check your internet connection and try again" + exit 1 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 "$@" From 45530e2e416479d3d3efe099ae2c6781cb0b8b48 Mon Sep 17 00:00:00 2001 From: DennisYu07 <617072224@qq.com> Date: Mon, 23 Feb 2026 20:03:03 -0800 Subject: [PATCH 6/6] add bash for debain --- .../installation/install-qwen-with-source.sh | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/scripts/installation/install-qwen-with-source.sh b/scripts/installation/install-qwen-with-source.sh index 15164b391be..0991ec48511 100755 --- a/scripts/installation/install-qwen-with-source.sh +++ b/scripts/installation/install-qwen-with-source.sh @@ -8,29 +8,35 @@ # install-qwen-with-source.sh -s [github|npm|internal|local-build] # 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 +# This block must use POSIX-compliant syntax ([ not [[) since it runs before we know bash is available +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 + case "${0}" in + *.git/hooks/*) export __QWEN_IN_GIT_HOOK=1 ;; + esac + if [ -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 + # Re-exec with bash, preserving all arguments + exec bash -- "${0}" "$@" else echo "Error: This script requires bash. Please install bash first." exit 1 fi fi -set -eo pipefail +# Enable strict mode (bash-specific options) +# pipefail requires bash 3+; check before setting +if [ -n "${BASH_VERSION:-}" ]; then + # shellcheck disable=SC3040 + set -eo pipefail +else + set -e +fi # ============================================ # Color definitions