#!/bin/bash # ============================================ # LinkMaster 节点端一键安装脚本 # 使用方法: curl -fsSL https://gitee.nas.cpolar.cn/yoyo/linkmaster-node/raw/branch/main/install.sh | bash -s -- <后端地址> # 示例: curl -fsSL https://gitee.nas.cpolar.cn/yoyo/linkmaster-node/raw/branch/main/install.sh | bash -s -- http://192.168.1.100:8080 # ============================================ set -e # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # 配置 # 尝试从脚本URL自动提取仓库信息(如果通过curl下载) SCRIPT_URL="${SCRIPT_URL:-}" if [ -z "$SCRIPT_URL" ] && [ -n "${BASH_SOURCE[0]}" ]; then # 如果脚本是通过 curl 下载的,尝试从环境变量获取 SCRIPT_URL="${SCRIPT_URL:-}" fi # 默认配置(如果无法自动提取,使用这些默认值) GITHUB_REPO="${GITHUB_REPO:-yoyo/linkmaster-node}" # 默认仓库(独立的 node 项目) GITHUB_BRANCH="${GITHUB_BRANCH:-main}" # 默认分支 SOURCE_DIR="/opt/linkmaster-node" # 源码目录 BINARY_NAME="linkmaster-node" INSTALL_DIR="/usr/local/bin" SERVICE_NAME="linkmaster-node" # 获取后端地址参数 BACKEND_URL="${1:-}" if [ -z "$BACKEND_URL" ]; then echo -e "${RED}错误: 请提供后端服务器地址${NC}" echo -e "${YELLOW}使用方法:${NC}" echo " curl -fsSL https://gitee.nas.cpolar.cn/${GITHUB_REPO}/raw/branch/${GITHUB_BRANCH}/install.sh | bash -s -- http://your-backend-server:8080" echo "" echo -e "${YELLOW}注意:${NC}" echo " - 节点端需要直接连接后端服务器,不是前端地址" echo " - 后端默认端口: 8080" echo " - 如果节点和后端在同一服务器: http://localhost:8080" echo " - 如果节点和后端在不同服务器: http://backend-ip:8080 或 http://backend-domain:8080" exit 1 fi # 检测系统类型和架构 detect_system() { if [ -f /etc/os-release ]; then . /etc/os-release OS=$ID OS_VERSION=$VERSION_ID else echo -e "${RED}无法检测系统类型${NC}" exit 1 fi ARCH=$(uname -m) case $ARCH in x86_64) ARCH="amd64" ;; aarch64|arm64) ARCH="arm64" ;; *) echo -e "${RED}不支持的架构: $ARCH${NC}" exit 1 ;; esac echo -e "${BLUE}检测到系统: $OS $OS_VERSION ($ARCH)${NC}" } # 检测并选择最快的镜像源 detect_fastest_mirror() { echo -e "${BLUE}检测最快的镜像源...${NC}" # Ubuntu/Debian 镜像源列表 UBUNTU_MIRRORS=( "mirrors.tuna.tsinghua.edu.cn" "mirrors.huaweicloud.com" "mirrors.163.com" "archive.ubuntu.com" ) # CentOS/RHEL 镜像源列表 CENTOS_MIRRORS=( "mirrors.tuna.tsinghua.edu.cn" "mirrors.huaweicloud.com" ) FASTEST_MIRROR="" FASTEST_TIME=999999 # 根据系统类型选择镜像源列表 if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ]; then MIRRORS=("${UBUNTU_MIRRORS[@]}") MIRROR_TYPE="ubuntu" elif [ "$OS" = "centos" ] || [ "$OS" = "rhel" ] || [ "$OS" = "rocky" ] || [ "$OS" = "almalinux" ]; then MIRRORS=("${CENTOS_MIRRORS[@]}") MIRROR_TYPE="centos" else echo -e "${YELLOW}⚠ 未知系统类型,跳过镜像源检测${NC}" return fi # 检查是否有测试工具 if ! command -v ping > /dev/null 2>&1 && ! command -v curl > /dev/null 2>&1; then echo -e "${YELLOW}⚠ 未找到 ping 或 curl 命令,跳过镜像源检测,使用默认源${NC}" return fi # 测试每个镜像源的速度 echo -e "${BLUE}正在测试镜像源速度...${NC}" for mirror in "${MIRRORS[@]}"; do echo -n " 测试 ${mirror}... " # 优先使用 ping 测试延迟(取平均值) if command -v ping > /dev/null 2>&1; then # ping 3次,取平均时间(macOS 使用 -W,Linux 使用 -w) if [[ "$OSTYPE" == "darwin"* ]]; then PING_RESULT=$(ping -c 3 -W 2000 "$mirror" 2>/dev/null | grep "avg" | awk -F'/' '{print $5}') else PING_RESULT=$(ping -c 3 -w 2 "$mirror" 2>/dev/null | grep "avg" | awk -F'/' '{print $5}') fi if [ -n "$PING_RESULT" ] && [ "$PING_RESULT" != "0" ]; then TIME=$(echo "$PING_RESULT" | awk '{print int($1)}') echo -e "${GREEN}${TIME}ms${NC}" if [ "$TIME" -lt "$FASTEST_TIME" ] 2>/dev/null; then FASTEST_TIME=$TIME FASTEST_MIRROR=$mirror fi else echo -e "${RED}超时${NC}" fi elif command -v curl > /dev/null 2>&1; then # 如果没有 ping 命令,使用 curl 测试连接时间 TIME=$(curl -o /dev/null -s -w "%{time_total}" --connect-timeout 3 "http://${mirror}" 2>/dev/null | awk '{print int($1*1000)}') if [ -n "$TIME" ] && [ "$TIME" -gt 0 ] && [ "$TIME" -lt 10000 ]; then echo -e "${GREEN}${TIME}ms${NC}" if [ "$TIME" -lt "$FASTEST_TIME" ]; then FASTEST_TIME=$TIME FASTEST_MIRROR=$mirror fi else echo -e "${RED}超时${NC}" fi fi done if [ -z "$FASTEST_MIRROR" ]; then echo -e "${YELLOW}⚠ 无法检测到可用镜像源,使用默认源${NC}" return fi echo -e "${GREEN}✓ 最快镜像源: ${FASTEST_MIRROR} (${FASTEST_TIME}ms)${NC}" # 配置镜像源 if [ "$MIRROR_TYPE" = "ubuntu" ]; then configure_ubuntu_mirror "$FASTEST_MIRROR" elif [ "$MIRROR_TYPE" = "centos" ]; then configure_centos_mirror "$FASTEST_MIRROR" fi } # 配置 Ubuntu/Debian 镜像源 configure_ubuntu_mirror() { local mirror="$1" local sources_file="/etc/apt/sources.list" local sources_backup="/etc/apt/sources.list.backup.$(date +%Y%m%d_%H%M%S)" # 备份原始源配置 if [ -f "$sources_file" ] && [ ! -f "$sources_backup" ]; then echo -e "${BLUE}备份原始源配置...${NC}" sudo cp "$sources_file" "$sources_backup" fi # 检测 Ubuntu 版本代号 if [ -f /etc/os-release ]; then . /etc/os-release CODENAME=$(echo "$VERSION_CODENAME" | tr '[:upper:]' '[:lower:]') if [ -z "$CODENAME" ]; then # 尝试从版本号推断 case "$VERSION_ID" in "22.04") CODENAME="jammy" ;; "20.04") CODENAME="focal" ;; "18.04") CODENAME="bionic" ;; *) CODENAME="jammy" ;; # 默认使用 jammy esac fi else CODENAME="jammy" # 默认 fi # 检测是 Ubuntu 还是 Debian if [ "$OS" = "ubuntu" ]; then echo -e "${BLUE}配置 Ubuntu 镜像源: ${mirror}${NC}" sudo tee "$sources_file" > /dev/null < /dev/null </dev/null || true fi # 检测系统类型和版本 local release_file="/etc/redhat-release" local os_version="" local os_name="centos" if [ -f "$release_file" ]; then RELEASE_CONTENT=$(cat "$release_file") os_version=$(echo "$RELEASE_CONTENT" | grep -oE '[0-9]+' | head -1) # 检测系统类型 if echo "$RELEASE_CONTENT" | grep -qi "rocky"; then os_name="rocky" elif echo "$RELEASE_CONTENT" | grep -qi "almalinux"; then os_name="almalinux" elif echo "$RELEASE_CONTENT" | grep -qi "centos"; then os_name="centos" fi else os_version="7" fi echo -e "${BLUE}配置 ${os_name} ${os_version} 镜像源: ${mirror}${NC}" # 备份并禁用所有现有仓库 sudo find "$repo_dir" -name "*.repo" -not -name "*.backup" -exec sudo mv {} {}.backup \; 2>/dev/null || true # 根据系统类型创建新的仓库配置 if [ "$os_name" = "rocky" ]; then # Rocky Linux sudo tee "$repo_dir/Rocky-Base.repo" > /dev/null < /dev/null < /dev/null < /dev/null </dev/null; then download_success=true else # 尝试从官方源下载 echo -e "${BLUE}尝试从 Go 官方源下载...${NC}" go_url="https://go.dev/dl/${go_tar}" if curl -fsSL -o "/tmp/${go_tar}" "${go_url}" 2>/dev/null; then download_success=true fi fi if [ "$download_success" = false ]; then echo -e "${RED}下载 Go 失败,请检查网络连接${NC}" return 1 fi # 删除旧版本(如果存在) if [ -d "/usr/local/go" ]; then echo -e "${BLUE}删除旧版本 Go...${NC}" sudo rm -rf /usr/local/go fi # 解压安装 echo -e "${BLUE}解压并安装 Go...${NC}" sudo tar -C /usr/local -xzf "/tmp/${go_tar}" rm -f "/tmp/${go_tar}" echo -e "${GREEN}✓ Go 解压完成${NC}" # 添加到 PATH(如果还没有) if ! grep -q "/usr/local/go/bin" /etc/profile 2>/dev/null; then echo 'export PATH=$PATH:/usr/local/go/bin' | sudo tee -a /etc/profile > /dev/null fi # 配置 Go 代理(使用国内镜像) if ! grep -q "GOPROXY" /etc/profile 2>/dev/null; then echo '' | sudo tee -a /etc/profile > /dev/null echo '# Go 代理配置(使用国内镜像加速)' | sudo tee -a /etc/profile > /dev/null echo 'export GOPROXY=https://goproxy.cn,direct' | sudo tee -a /etc/profile > /dev/null echo 'export GOSUMDB=off' | sudo tee -a /etc/profile > /dev/null fi # 设置当前会话的 PATH 和 GOPROXY export PATH=$PATH:/usr/local/go/bin export GOPROXY=https://goproxy.cn,direct export GOSUMDB=off # 验证安装 if command -v go > /dev/null 2>&1; then GO_VERSION=$(go version 2>/dev/null | head -1) echo -e "${GREEN}✓ Go 安装完成: ${GO_VERSION}${NC}" return 0 else echo -e "${YELLOW}⚠ Go 已安装但未在 PATH 中,请重新登录或执行: export PATH=\$PATH:/usr/local/go/bin${NC}" # 临时添加到 PATH 继续执行 export PATH=$PATH:/usr/local/go/bin if command -v go > /dev/null 2>&1; then GO_VERSION=$(go version 2>/dev/null | head -1) echo -e "${GREEN}✓ Go 安装完成: ${GO_VERSION}${NC}" return 0 fi return 1 fi } # 安装 Go 环境 install_go() { echo -e "${BLUE}检查 Go 环境...${NC}" # 先检查是否已安装且可用 if command -v go > /dev/null 2>&1; then GO_VERSION=$(go version 2>/dev/null | head -1) if [ -n "$GO_VERSION" ]; then echo -e "${GREEN}✓ Go 已安装: ${GO_VERSION}${NC}" # 检查 Go 版本是否可用(尝试运行 go version) if go version > /dev/null 2>&1; then echo -e "${BLUE}Go 环境正常,跳过安装流程${NC}" # 确保 GOPROXY 已配置(如果未配置则添加) if ! grep -q "GOPROXY" /etc/profile 2>/dev/null; then echo -e "${BLUE}配置 Go 代理环境变量...${NC}" echo '' | sudo tee -a /etc/profile > /dev/null echo '# Go 代理配置(使用国内镜像加速)' | sudo tee -a /etc/profile > /dev/null echo 'export GOPROXY=https://goproxy.cn,direct' | sudo tee -a /etc/profile > /dev/null echo 'export GOSUMDB=off' | sudo tee -a /etc/profile > /dev/null fi # 设置当前会话的环境变量 export GOPROXY=${GOPROXY:-https://goproxy.cn,direct} export GOSUMDB=${GOSUMDB:-off} return 0 else echo -e "${YELLOW}⚠ Go 已安装但无法正常运行,尝试重新安装...${NC}" fi else echo -e "${YELLOW}⚠ Go 已安装但无法获取版本信息,尝试重新安装...${NC}" fi fi # 如果 Go 未安装或不可用,开始安装流程 echo -e "${BLUE}开始安装 Go 环境...${NC}" # 尝试从系统包管理器安装 local install_success=false if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ]; then echo -e "${BLUE}尝试从 apt 安装 Go...${NC}" sudo apt-get update if sudo apt-get install -y golang-go; then install_success=true else echo -e "${YELLOW}⚠ apt 安装 Go 失败${NC}" fi elif [ "$OS" = "centos" ] || [ "$OS" = "rhel" ] || [ "$OS" = "rocky" ] || [ "$OS" = "almalinux" ]; then echo -e "${BLUE}尝试从 yum 安装 Go...${NC}" # 检查 yum 源中是否有 golang 包 if yum list available golang > /dev/null 2>&1; then if sudo yum install -y golang; then install_success=true else echo -e "${YELLOW}⚠ yum 安装 Go 失败${NC}" fi else echo -e "${YELLOW}⚠ yum 源中未找到 golang 包,尝试从官网下载安装${NC}" fi fi # 检查安装是否成功 if [ "$install_success" = true ] && command -v go > /dev/null 2>&1; then GO_VERSION=$(go version 2>/dev/null | head -1) echo -e "${GREEN}✓ Go 安装完成: ${GO_VERSION}${NC}" # 配置 Go 代理(使用国内镜像) if ! grep -q "GOPROXY" /etc/profile 2>/dev/null; then echo '' | sudo tee -a /etc/profile > /dev/null echo '# Go 代理配置(使用国内镜像加速)' | sudo tee -a /etc/profile > /dev/null echo 'export GOPROXY=https://goproxy.cn,direct' | sudo tee -a /etc/profile > /dev/null echo 'export GOSUMDB=off' | sudo tee -a /etc/profile > /dev/null fi # 设置当前会话的 GOPROXY export GOPROXY=https://goproxy.cn,direct export GOSUMDB=off return 0 fi # 如果包管理器安装失败,从官网下载安装 echo -e "${BLUE}包管理器安装失败,尝试从官网下载安装...${NC}" if install_go_from_official; then return 0 fi # 所有方法都失败 echo -e "${RED}Go 安装失败${NC}" show_build_alternatives exit 1 } # 显示替代方案 show_build_alternatives() { echo "" echo -e "${YELLOW}═══════════════════════════════════════════════════════════${NC}" echo -e "${YELLOW} 安装失败,请使用以下替代方案:${NC}" echo -e "${YELLOW}═══════════════════════════════════════════════════════════${NC}" echo "" echo -e "${GREEN}手动编译安装:${NC}" echo " git clone https://gitee.nas.cpolar.cn/${GITHUB_REPO}.git ${SOURCE_DIR}" echo " cd ${SOURCE_DIR}" echo " go build -o agent ./cmd/agent" echo " sudo cp agent /usr/local/bin/linkmaster-node" echo " sudo chmod +x /usr/local/bin/linkmaster-node" echo "" } # 检查是否已安装 check_installed() { # 检查服务文件是否存在 if [ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]; then return 0 fi # 检查二进制文件是否存在 if [ -f "$INSTALL_DIR/$BINARY_NAME" ]; then return 0 fi # 检查源码目录是否存在 if [ -d "$SOURCE_DIR" ]; then return 0 fi return 1 } # 卸载已安装的服务 uninstall_service() { echo -e "${BLUE}检测到已安装的服务,开始卸载...${NC}" # 停止服务 if systemctl is-active --quiet ${SERVICE_NAME} 2>/dev/null; then echo -e "${BLUE}停止服务...${NC}" sudo systemctl stop ${SERVICE_NAME} 2>/dev/null || true sleep 2 fi # 禁用服务 if systemctl is-enabled --quiet ${SERVICE_NAME} 2>/dev/null; then echo -e "${BLUE}禁用服务...${NC}" sudo systemctl disable ${SERVICE_NAME} 2>/dev/null || true fi # 删除 systemd 服务文件 if [ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]; then echo -e "${BLUE}删除服务文件...${NC}" sudo rm -f /etc/systemd/system/${SERVICE_NAME}.service fi # 删除可能的 override 配置目录(包含 Environment 等配置) if [ -d "/etc/systemd/system/${SERVICE_NAME}.service.d" ]; then echo -e "${BLUE}删除服务配置目录...${NC}" sudo rm -rf /etc/systemd/system/${SERVICE_NAME}.service.d fi # 重新加载 systemd daemon sudo systemctl daemon-reload # 删除二进制文件 if [ -f "$INSTALL_DIR/$BINARY_NAME" ]; then echo -e "${BLUE}删除二进制文件...${NC}" sudo rm -f "$INSTALL_DIR/$BINARY_NAME" fi # 删除源码目录 if [ -d "$SOURCE_DIR" ]; then echo -e "${BLUE}删除源码目录...${NC}" sudo rm -rf "$SOURCE_DIR" fi # 清理进程(如果还在运行) if pgrep -f "$BINARY_NAME" > /dev/null 2>&1; then echo -e "${BLUE}清理残留进程...${NC}" sudo pkill -f "$BINARY_NAME" 2>/dev/null || true sleep 1 fi echo -e "${GREEN}✓ 卸载完成${NC}" echo "" } # 从源码编译安装 build_from_source() { echo -e "${BLUE}从源码编译安装节点端...${NC}" # 检查 Go 环境(如果已安装则跳过安装流程) if ! command -v go > /dev/null 2>&1; then echo -e "${BLUE}未检测到 Go 环境,开始安装...${NC}" install_go else # Go 已安装,验证是否可用 GO_VERSION=$(go version 2>/dev/null | head -1 || echo "") if [ -n "$GO_VERSION" ] && go version > /dev/null 2>&1; then echo -e "${GREEN}✓ Go 已安装: ${GO_VERSION}${NC}" echo -e "${BLUE}跳过 Go 安装流程,直接使用现有环境${NC}" else echo -e "${YELLOW}⚠ Go 已安装但无法正常运行,尝试重新安装...${NC}" install_go fi fi # 确保 Go 在 PATH 中(如果从官网安装的) if [ -d "/usr/local/go/bin" ] && ! echo "$PATH" | grep -q "/usr/local/go/bin"; then export PATH=$PATH:/usr/local/go/bin fi # 再次检查 Go 版本(确保安装成功) GO_VERSION=$(go version 2>/dev/null | head -1 || echo "") if [ -z "$GO_VERSION" ]; then echo -e "${RED}无法获取 Go 版本信息${NC}" show_build_alternatives exit 1 fi echo -e "${BLUE}使用 Go 版本: ${GO_VERSION}${NC}" # 确定 Go 的完整路径(用于 sudo 执行) GO_BIN=$(command -v go) if [ -z "$GO_BIN" ]; then echo -e "${RED}无法找到 Go 可执行文件${NC}" show_build_alternatives exit 1 fi # 如果源码目录已存在,删除(卸载函数应该已经删除,这里作为保险) if [ -d "$SOURCE_DIR" ]; then echo -e "${YELLOW}清理旧的源码目录...${NC}" sudo rm -rf "$SOURCE_DIR" fi # 克隆仓库到源码目录 echo -e "${BLUE}克隆仓库到 ${SOURCE_DIR}...${NC}" if ! sudo git clone --branch "${GITHUB_BRANCH}" "https://gitee.nas.cpolar.cn/${GITHUB_REPO}.git" "$SOURCE_DIR" 2>&1; then echo -e "${RED}克隆仓库失败,请检查网络连接和仓库地址${NC}" echo -e "${YELLOW}仓库地址: https://gitee.nas.cpolar.cn/${GITHUB_REPO}.git${NC}" show_build_alternatives exit 1 fi # 设置目录权限(允许当前用户访问,但服务运行时是 root) sudo chown -R root:root "$SOURCE_DIR" 2>/dev/null || true cd "$SOURCE_DIR" # 配置 Git safe.directory,解决所有权问题 sudo git config --global --add safe.directory "$SOURCE_DIR" 2>/dev/null || true git config --global --add safe.directory "$SOURCE_DIR" 2>/dev/null || true # 配置 Git 使用代理(加速 GitHub 访问) echo -e "${BLUE}配置 Git 代理(加速 GitHub 访问)...${NC}" # GitHub 代理镜像列表(按优先级排序) GITHUB_PROXIES=( "https://gh-proxy.com" "https://githubproxy.cc" "https://ghproxy.com" "https://mirror.ghproxy.com" "https://github.com.cnpmjs.org" ) # 测试并选择可用的 GitHub 代理 GITHUB_PROXY="" for proxy in "${GITHUB_PROXIES[@]}"; do echo -n " 测试 ${proxy}... " # 测试代理是否可用(尝试访问 GitHub 主页) if curl -sf --connect-timeout 3 --max-time 5 "${proxy}/https://github.com" > /dev/null 2>&1 || \ curl -sf --connect-timeout 3 --max-time 5 "${proxy}" > /dev/null 2>&1; then GITHUB_PROXY="$proxy" echo -e "${GREEN}可用${NC}" break else echo -e "${RED}不可用${NC}" fi done if [ -n "$GITHUB_PROXY" ]; then # 配置 Git 使用代理 echo -e "${GREEN}✓ 使用 GitHub 代理: ${GITHUB_PROXY}${NC}" # 根据不同的代理服务配置不同的 URL 格式 if echo "$GITHUB_PROXY" | grep -q "gh-proxy.com\|githubproxy.cc"; then # gh-proxy.com 和 githubproxy.cc 使用 /https://github.com/ 格式 GIT_PROXY_URL="${GITHUB_PROXY}/https://github.com/" sudo git config --global url."${GIT_PROXY_URL}".insteadOf "https://github.com/" 2>/dev/null || true git config --global url."${GIT_PROXY_URL}".insteadOf "https://github.com/" 2>/dev/null || true # 配置 Git HTTP/HTTPS 代理 sudo git config --global http.https://github.com.proxy "${GITHUB_PROXY}/" 2>/dev/null || true git config --global http.https://github.com.proxy "${GITHUB_PROXY}/" 2>/dev/null || true else # 其他代理使用标准格式 sudo git config --global url."${GITHUB_PROXY}/https://github.com/".insteadOf "https://github.com/" 2>/dev/null || true git config --global url."${GITHUB_PROXY}/https://github.com/".insteadOf "https://github.com/" 2>/dev/null || true sudo git config --global http.https://github.com.proxy "${GITHUB_PROXY}/" 2>/dev/null || true git config --global http.https://github.com.proxy "${GITHUB_PROXY}/" 2>/dev/null || true fi # 设置环境变量(供 Go 使用) export HTTP_PROXY="${GITHUB_PROXY}/" export HTTPS_PROXY="${GITHUB_PROXY}/" export http_proxy="${GITHUB_PROXY}/" export https_proxy="${GITHUB_PROXY}/" # 添加到 Go 环境变量中 GO_ENV="$GO_ENV HTTP_PROXY=${GITHUB_PROXY}/ HTTPS_PROXY=${GITHUB_PROXY}/ http_proxy=${GITHUB_PROXY}/ https_proxy=${GITHUB_PROXY}/" echo -e "${BLUE}已配置 Git 和 Go 使用 GitHub 代理加速${NC}" else echo -e "${YELLOW}⚠ 未找到可用的 GitHub 代理,将直接访问 GitHub${NC}" echo -e "${YELLOW}提示: 如果 GitHub 访问慢,可以手动设置代理${NC}" fi # 配置 Go 代理(使用国内镜像) echo -e "${BLUE}配置 Go 代理(使用国内镜像加速)...${NC}" # Go 代理镜像列表(按优先级排序,使用多个镜像以提高成功率) # 注意:不包含 proxy.golang.org,因为在中国大陆无法访问 GO_PROXY_MIRRORS=( "https://goproxy.cn,https://goproxy.io,https://mirrors.aliyun.com/go-proxy/,direct" "https://goproxy.io,https://goproxy.cn,https://mirrors.aliyun.com/go-proxy/,direct" "https://mirrors.aliyun.com/go-proxy/,https://goproxy.cn,https://goproxy.io,direct" ) # 测试并选择最快的 Go 代理(使用多个镜像作为备选) GO_PROXY="" for proxy_list in "${GO_PROXY_MIRRORS[@]}"; do # 取第一个镜像进行测试 first_proxy=$(echo "$proxy_list" | cut -d',' -f1) echo -n " 测试代理链: ${first_proxy}... " if curl -sf --connect-timeout 3 --max-time 5 "${first_proxy}" > /dev/null 2>&1; then GO_PROXY="$proxy_list" echo -e "${GREEN}可用${NC}" break else echo -e "${RED}不可用${NC}" fi done # 如果所有镜像都不可用,使用 direct 模式(直接从源码仓库下载) if [ -z "$GO_PROXY" ]; then GO_PROXY="direct" echo -e "${YELLOW}⚠ 所有国内镜像不可用,使用 direct 模式(直接从 GitHub/GitLab 等下载)${NC}" echo -e "${YELLOW}⚠ 注意: direct 模式可能较慢,建议检查网络连接${NC}" else echo -e "${GREEN}✓ 使用 Go 代理链: ${GO_PROXY}${NC}" echo -e "${BLUE}说明: Go 会按顺序尝试代理列表中的镜像,最后使用 direct 模式${NC}" echo -e "${BLUE}注意: 已禁用 GOSUMDB,避免连接 proxy.golang.org${NC}" fi # 下载依赖(使用 sudo 以 root 用户执行,确保 PATH 包含 Go,并设置 GOPROXY) echo -e "${BLUE}下载 Go 依赖...${NC}" echo -e "${BLUE}使用代理: ${GO_PROXY}${NC}" # 构建包含 Go PATH 和 GOPROXY 的环境变量 GO_PATH_ENV="PATH=\$PATH:/usr/local/go/bin" if [ -d "/usr/local/go/bin" ]; then GO_PATH_ENV="PATH=/usr/local/go/bin:\$PATH" fi # 禁用 GOSUMDB 以避免连接 proxy.golang.org(在中国大陆通常无法访问) # 如果必须启用校验和,可以使用 GOSUMDB=sum.golang.google.cn,但可能仍有问题 GO_ENV="$GO_PATH_ENV GOPROXY=${GO_PROXY} GOSUMDB=off GOTIMEOUT=60 GONOPROXY= GONOSUMDB= GOPRIVATE=*" # 使用 go env -w 永久设置环境变量(避免被其他配置覆盖) echo -e "${BLUE}永久设置 Go 环境变量(避免连接 proxy.golang.org)...${NC}" sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPROXY=${GO_PROXY}" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOSUMDB=off" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPRIVATE=*" 2>/dev/null || true # 显示当前 Go 环境信息(验证设置是否生效) echo -e "${BLUE}Go 环境信息:${NC}" echo -e "${BLUE} 设置的 GOPROXY=${GO_PROXY}${NC}" echo -e "${BLUE} 设置的 GOSUMDB=off (已禁用,避免连接 proxy.golang.org)${NC}" echo -e "${BLUE} 实际生效的环境变量:${NC}" sudo bash -c "cd '$SOURCE_DIR' && $GO_ENV && go env GOPROXY GOSUMDB GOPRIVATE" || true echo "" # 验证是否仍然会连接 proxy.golang.org ACTUAL_GOPROXY=$(sudo bash -c "cd '$SOURCE_DIR' && $GO_ENV && go env GOPROXY" 2>/dev/null || echo "") if echo "$ACTUAL_GOPROXY" | grep -q "proxy.golang.org"; then echo -e "${RED}⚠ 警告: 检测到 GOPROXY 仍包含 proxy.golang.org${NC}" echo -e "${YELLOW}正在强制移除 proxy.golang.org...${NC}" CLEAN_PROXY=$(echo "$ACTUAL_GOPROXY" | sed 's|https://proxy.golang.org,||g' | sed 's|,https://proxy.golang.org||g' | sed 's|https://proxy.golang.org||g') GO_PROXY="$CLEAN_PROXY" GO_ENV="$GO_PATH_ENV GOPROXY=${GO_PROXY} GOSUMDB=off GOTIMEOUT=60 GONOPROXY= GONOSUMDB= GOPRIVATE=*" sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPROXY=${GO_PROXY}" 2>/dev/null || true echo -e "${GREEN}✓ 已更新 GOPROXY: ${GO_PROXY}${NC}" fi # 先尝试整理依赖(显示详细信息) echo -e "${BLUE}整理 Go 模块依赖...${NC}" # 再次确认环境变量(防止被覆盖) sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPROXY=${GO_PROXY}" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOSUMDB=off" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPRIVATE=*" 2>/dev/null || true if ! sudo bash -c "cd '$SOURCE_DIR' && $GO_ENV && go mod tidy -v" 2>&1; then echo -e "${YELLOW}⚠ go mod tidy 失败,继续尝试下载依赖...${NC}" # 检查错误日志中是否包含 proxy.golang.org TIDY_LOG=$(sudo bash -c "cd '$SOURCE_DIR' && $GO_ENV && go mod tidy -v" 2>&1 || true) if echo "$TIDY_LOG" | grep -q "proxy.golang.org"; then echo -e "${RED}错误: 检测到仍尝试连接 proxy.golang.org${NC}" echo -e "${YELLOW}尝试清理 Go 模块缓存并重新设置...${NC}" sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go clean -modcache" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPROXY=${GO_PROXY}" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOSUMDB=off" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPRIVATE=*" 2>/dev/null || true fi fi # 下载依赖(显示详细进度和速度) echo -e "${BLUE}开始下载 Go 依赖包(这可能需要一些时间)...${NC}" echo -e "${YELLOW}提示: 如果下载速度慢,请检查网络连接和代理设置${NC}" echo "" # 设置更长的超时时间和启用详细输出 DOWNLOAD_START_TIME=$(date +%s) DOWNLOAD_LOG="/tmp/go_download_$$.log" DOWNLOAD_PROGRESS_LOG="/tmp/go_progress_$$.log" # 在下载前再次确认环境变量设置(强制所有包都通过代理) sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPROXY=${GO_PROXY}" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOSUMDB=off" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GONOPROXY=" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GONOSUMDB=" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPRIVATE=*" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOTIMEOUT=60" 2>/dev/null || true # 使用命名管道来实时监控输出 PIPE_FILE="/tmp/go_download_pipe_$$" mkfifo "$PIPE_FILE" 2>/dev/null || true # 后台进程显示实时进度 ( DOWNLOADED_COUNT=0 LAST_COUNT=0 LAST_PACKAGE="" PACKAGES=() # 从管道读取输出并实时显示 while IFS= read -r line || [ -n "$line" ]; do # 检查是否结束 if [ -f "${DOWNLOAD_PROGRESS_LOG}.done" ]; then break fi # 检测下载消息 if echo "$line" | grep -q "go: downloading"; then DOWNLOADED_COUNT=$((DOWNLOADED_COUNT + 1)) CURRENT_TIME=$(date +%s) ELAPSED=$((CURRENT_TIME - DOWNLOAD_START_TIME)) # 提取包名 PACKAGE_NAME=$(echo "$line" | sed 's/go: downloading //' | cut -d' ' -f1) if [ -n "$PACKAGE_NAME" ] && [ "$PACKAGE_NAME" != "$LAST_PACKAGE" ]; then PACKAGES+=("$PACKAGE_NAME") LAST_PACKAGE=$PACKAGE_NAME # 计算速度 if [ "$ELAPSED" -gt 0 ]; then SPEED=$(echo "scale=2; $DOWNLOADED_COUNT / $ELAPSED" | bc 2>/dev/null || echo "0") else SPEED="0" fi # 显示进度(使用 \r 在同一行更新) echo -ne "\r${BLUE}进度: 已下载 ${GREEN}${DOWNLOADED_COUNT}${NC} 个包 | 耗时: ${ELAPSED}秒 | 速度: ${GREEN}${SPEED}${NC} 包/秒 | 当前: ${GREEN}${PACKAGE_NAME}${NC} " fi fi done < "$PIPE_FILE" & # 等待主进程结束 while [ ! -f "${DOWNLOAD_PROGRESS_LOG}.done" ]; do sleep 0.5 done ) & PROGRESS_PID=$! # 使用 -x 标志下载,显示详细输出,同时写入日志和管道 # 设置较短的超时时间,避免长时间卡住 echo -e "${BLUE}开始下载(超时时间:300秒)...${NC}" # 确保环境变量中包含代理设置 if [ -n "$GITHUB_PROXY" ]; then GO_ENV="$GO_ENV HTTP_PROXY=${GITHUB_PROXY}/ HTTPS_PROXY=${GITHUB_PROXY}/ http_proxy=${GITHUB_PROXY}/ https_proxy=${GITHUB_PROXY}/" fi # 使用 timeout 命令设置总超时时间 if timeout 300 sudo bash -c "cd '$SOURCE_DIR' && $GO_ENV && go mod download -x" 2>&1 | tee "$DOWNLOAD_LOG" "$PIPE_FILE"; then # 停止进度显示进程 touch "${DOWNLOAD_PROGRESS_LOG}.done" sleep 2 kill "$PROGRESS_PID" 2>/dev/null || true wait "$PROGRESS_PID" 2>/dev/null || true # 清理管道文件 rm -f "$PIPE_FILE" 2>/dev/null || true DOWNLOAD_END_TIME=$(date +%s) DOWNLOAD_DURATION=$((DOWNLOAD_END_TIME - DOWNLOAD_START_TIME)) # 统计下载信息(从输出中统计) TOTAL_PACKAGES=$(grep -c "go: downloading" "$DOWNLOAD_LOG" 2>/dev/null || echo "0") SUCCESS_COUNT=$(grep -c "go: downloading" "$DOWNLOAD_LOG" 2>/dev/null || echo "0") echo "" echo -e "${GREEN}✓ 依赖下载完成${NC}" echo -e "${BLUE}下载统计:${NC}" echo -e " 总包数: ${GREEN}${TOTAL_PACKAGES}${NC}" echo -e " 成功下载: ${GREEN}${SUCCESS_COUNT}${NC}" echo -e " 总耗时: ${GREEN}${DOWNLOAD_DURATION}${NC}秒" if [ "$DOWNLOAD_DURATION" -gt 0 ] && [ "$SUCCESS_COUNT" -gt 0 ]; then AVG_SPEED=$(echo "scale=2; $SUCCESS_COUNT / $DOWNLOAD_DURATION" | bc 2>/dev/null || echo "0") echo -e " 平均速度: ${GREEN}${AVG_SPEED}${NC} 包/秒" fi # 显示最后下载的几个包 echo -e "${BLUE}最后下载的包:${NC}" grep "go: downloading" "$DOWNLOAD_LOG" 2>/dev/null | tail -10 | sed 's/go: downloading \([^ ]*\).*/ ✓ \1/' | while IFS= read -r line; do echo "$line" done || true else # 停止进度显示进程 touch "${DOWNLOAD_PROGRESS_LOG}.done" sleep 2 kill "$PROGRESS_PID" 2>/dev/null || true wait "$PROGRESS_PID" 2>/dev/null || true # 清理管道文件 rm -f "$PIPE_FILE" 2>/dev/null || true DOWNLOAD_EXIT_CODE=${DOWNLOAD_EXIT_CODE:-$?} DOWNLOAD_END_TIME=$(date +%s) DOWNLOAD_DURATION=$((DOWNLOAD_END_TIME - DOWNLOAD_START_TIME)) # 统计已下载的包 DOWNLOADED_COUNT=$(grep -c "go: downloading" "$DOWNLOAD_LOG" 2>/dev/null || echo "0") if [ "$DOWNLOAD_EXIT_CODE" = "124" ]; then echo "" echo -e "${YELLOW}⚠ 下载超时 (${DOWNLOAD_DURATION}秒)${NC}" echo -e "${BLUE}已下载: ${GREEN}${DOWNLOADED_COUNT}${NC} 个包" echo -e "${YELLOW}继续尝试编译,如果失败请检查网络连接${NC}" else echo "" echo -e "${YELLOW}⚠ 使用国内镜像下载失败 (耗时: ${DOWNLOAD_DURATION}秒,退出码: ${DOWNLOAD_EXIT_CODE})${NC}" echo -e "${BLUE}已下载: ${GREEN}${DOWNLOADED_COUNT}${NC} 个包" echo -e "${YELLOW}最后30行日志:${NC}" tail -30 "$DOWNLOAD_LOG" 2>/dev/null || true echo "" echo -e "${YELLOW}尝试使用 direct 模式(直接从 GitHub/GitLab 等下载)...${NC}" # 如果失败,尝试使用 direct 模式,直接从源码仓库下载 # 注意:不使用 proxy.golang.org,因为在中国大陆无法访问 GO_ENV="$GO_PATH_ENV GOPROXY=direct GOSUMDB=off GOTIMEOUT=60 GONOPROXY= GONOSUMDB= GOPRIVATE=*" # 如果配置了 GitHub 代理,添加到环境变量 if [ -n "$GITHUB_PROXY" ]; then GO_ENV="$GO_ENV HTTP_PROXY=${GITHUB_PROXY}/ HTTPS_PROXY=${GITHUB_PROXY}/ http_proxy=${GITHUB_PROXY}/ https_proxy=${GITHUB_PROXY}/" fi # 重新设置环境变量 sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPROXY=direct" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOSUMDB=off" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GONOPROXY=" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GONOSUMDB=" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOPRIVATE=*" 2>/dev/null || true sudo bash -c "cd '$SOURCE_DIR' && $GO_PATH_ENV && go env -w GOTIMEOUT=60" 2>/dev/null || true # 重新启动进度显示 ( DOWNLOADED_COUNT=0 LAST_UPDATE_TIME=$(date +%s) LAST_COUNT=0 while [ ! -f "$DOWNLOAD_PROGRESS_LOG.done2" ]; do sleep 1 CURRENT_COUNT=$(grep -c "go: downloading" "$DOWNLOAD_LOG" 2>/dev/null || echo "0") if [ "$CURRENT_COUNT" -gt "$LAST_COUNT" ]; then DOWNLOADED_COUNT=$CURRENT_COUNT CURRENT_TIME=$(date +%s) ELAPSED=$((CURRENT_TIME - DOWNLOAD_START_TIME)) if [ "$ELAPSED" -gt 0 ]; then SPEED=$(echo "scale=2; $DOWNLOADED_COUNT / $ELAPSED" | bc 2>/dev/null || echo "0") echo -ne "\r${BLUE}进度: 已下载 ${GREEN}${DOWNLOADED_COUNT}${NC} 个包 | 耗时: ${ELAPSED}秒 | 速度: ${GREEN}${SPEED}${NC} 包/秒" fi LAST_COUNT=$CURRENT_COUNT fi CURRENT_PACKAGE=$(grep "go: downloading" "$DOWNLOAD_LOG" | tail -1 | sed 's/go: downloading //' | cut -d' ' -f1) if [ -n "$CURRENT_PACKAGE" ] && [ "$CURRENT_PACKAGE" != "$LAST_PACKAGE" ]; then echo "" echo -e "${BLUE} → 正在下载: ${GREEN}${CURRENT_PACKAGE}${NC}" LAST_PACKAGE=$CURRENT_PACKAGE fi done ) & PROGRESS_PID2=$! # 重新创建管道 rm -f "$PIPE_FILE" 2>/dev/null || true mkfifo "$PIPE_FILE" 2>/dev/null || true # 重新启动进度显示 ( DOWNLOADED_COUNT=0 LAST_COUNT=0 LAST_PACKAGE="" while IFS= read -r line || [ -n "$line" ]; do if [ -f "${DOWNLOAD_PROGRESS_LOG}.done2" ]; then break fi if echo "$line" | grep -q "go: downloading"; then DOWNLOADED_COUNT=$((DOWNLOADED_COUNT + 1)) CURRENT_TIME=$(date +%s) ELAPSED=$((CURRENT_TIME - DOWNLOAD_START_TIME)) PACKAGE_NAME=$(echo "$line" | sed 's/go: downloading //' | cut -d' ' -f1) if [ -n "$PACKAGE_NAME" ] && [ "$PACKAGE_NAME" != "$LAST_PACKAGE" ]; then LAST_PACKAGE=$PACKAGE_NAME if [ "$ELAPSED" -gt 0 ]; then SPEED=$(echo "scale=2; $DOWNLOADED_COUNT / $ELAPSED" | bc 2>/dev/null || echo "0") else SPEED="0" fi echo -ne "\r${BLUE}进度: 已下载 ${GREEN}${DOWNLOADED_COUNT}${NC} 个包 | 耗时: ${ELAPSED}秒 | 速度: ${GREEN}${SPEED}${NC} 包/秒 | 当前: ${GREEN}${PACKAGE_NAME}${NC} " fi fi done < "$PIPE_FILE" & while [ ! -f "${DOWNLOAD_PROGRESS_LOG}.done2" ]; do sleep 0.5 done ) & PROGRESS_PID2=$! DOWNLOAD_START_TIME=$(date +%s) if sudo bash -c "cd '$SOURCE_DIR' && $GO_ENV && timeout 600 go mod download -x" 2>&1 | tee "$DOWNLOAD_LOG" "$PIPE_FILE"; then # 停止进度显示进程 touch "${DOWNLOAD_PROGRESS_LOG}.done2" sleep 2 kill "$PROGRESS_PID2" 2>/dev/null || true wait "$PROGRESS_PID2" 2>/dev/null || true rm -f "$PIPE_FILE" 2>/dev/null || true # 停止进度显示进程 touch "$DOWNLOAD_PROGRESS_LOG.done2" sleep 1 kill "$PROGRESS_PID2" 2>/dev/null || true wait "$PROGRESS_PID2" 2>/dev/null || true DOWNLOAD_END_TIME=$(date +%s) DOWNLOAD_DURATION=$((DOWNLOAD_END_TIME - DOWNLOAD_START_TIME)) # 统计下载信息 TOTAL_PACKAGES=$(grep -c '"Path":' "$DOWNLOAD_LOG" 2>/dev/null || echo "0") SUCCESS_COUNT=$(grep -c '"Version":' "$DOWNLOAD_LOG" 2>/dev/null || echo "0") echo "" echo -e "${GREEN}✓ 依赖下载完成${NC}" echo -e "${BLUE}下载统计:${NC}" echo -e " 总包数: ${GREEN}${TOTAL_PACKAGES}${NC}" echo -e " 成功下载: ${GREEN}${SUCCESS_COUNT}${NC}" echo -e " 总耗时: ${GREEN}${DOWNLOAD_DURATION}${NC}秒" if [ "$DOWNLOAD_DURATION" -gt 0 ] && [ "$SUCCESS_COUNT" -gt 0 ]; then AVG_SPEED=$(echo "scale=2; $SUCCESS_COUNT / $DOWNLOAD_DURATION" | bc 2>/dev/null || echo "0") echo -e " 平均速度: ${GREEN}${AVG_SPEED}${NC} 包/秒" fi else echo -e "${RED}下载依赖失败,最后100行日志:${NC}" tail -100 "$DOWNLOAD_LOG" 2>/dev/null || true rm -f "$DOWNLOAD_LOG" show_build_alternatives exit 1 fi fi fi rm -f "$DOWNLOAD_LOG" 2>/dev/null || true # 编译到临时文件(在用户有权限的目录),然后移动到目标位置 echo -e "${BLUE}编译二进制文件...${NC}" TEMP_BINARY=$(mktemp) BINARY_PATH="$SOURCE_DIR/agent" # 使用 sudo 以 root 用户编译,直接输出到目标位置,确保 PATH 包含 Go,并设置 GOPROXY echo -e "${BLUE}开始编译(架构: ${ARCH})...${NC}" if sudo bash -c "cd '$SOURCE_DIR' && $GO_ENV && GOOS=linux GOARCH=${ARCH} CGO_ENABLED=0 go build -v -buildvcs=false -ldflags='-w -s' -o '$BINARY_PATH' ./cmd/agent" 2>&1 | tee /tmp/go_build.log; then if [ -f "$BINARY_PATH" ] && [ -s "$BINARY_PATH" ]; then sudo chmod +x "$BINARY_PATH" BINARY_SIZE=$(du -h "$BINARY_PATH" | cut -f1) echo -e "${GREEN}✓ 编译成功 (文件大小: ${BINARY_SIZE})${NC}" rm -f /tmp/go_build.log 2>/dev/null || true else echo -e "${RED}编译失败:未生成二进制文件${NC}" echo -e "${YELLOW}编译日志:${NC}" cat /tmp/go_build.log 2>/dev/null || true show_build_alternatives exit 1 fi else echo -e "${RED}编译失败,编译日志:${NC}" cat /tmp/go_build.log 2>/dev/null || true rm -f /tmp/go_build.log 2>/dev/null || true show_build_alternatives exit 1 fi # 清理临时文件 rm -f "$TEMP_BINARY" 2>/dev/null || true # 复制到安装目录(可选,保留在源码目录供 run.sh 使用) sudo mkdir -p "$INSTALL_DIR" sudo cp "$BINARY_PATH" "$INSTALL_DIR/$BINARY_NAME" sudo chmod +x "$INSTALL_DIR/$BINARY_NAME" echo -e "${GREEN}✓ 编译安装完成${NC}" echo -e "${BLUE}源码目录: ${SOURCE_DIR}${NC}" echo -e "${BLUE}二进制文件: ${INSTALL_DIR}/${BINARY_NAME}${NC}" } # 创建 systemd 服务 create_service() { echo -e "${BLUE}创建 systemd 服务...${NC}" # 确保启动脚本有执行权限 sudo chmod +x "$SOURCE_DIR/run.sh" sudo chmod +x "$SOURCE_DIR/start-systemd.sh" sudo tee /etc/systemd/system/${SERVICE_NAME}.service > /dev/null < /dev/null 2>&1; then if sudo systemctl is-active --quiet firewalld 2>/dev/null; then echo -e "${BLUE}检测到 firewalld,添加端口规则...${NC}" if sudo firewall-cmd --permanent --add-port=${PORT}/tcp > /dev/null 2>&1; then sudo firewall-cmd --reload > /dev/null 2>&1 echo -e "${GREEN}✓ firewalld 规则已添加${NC}" FIREWALL_CONFIGURED=true else echo -e "${YELLOW}⚠ firewalld 规则添加失败(可能需要手动配置)${NC}" fi fi fi # 检测 ufw (Ubuntu/Debian) if command -v ufw > /dev/null 2>&1; then if sudo ufw status > /dev/null 2>&1; then echo -e "${BLUE}检测到 ufw,添加端口规则...${NC}" if sudo ufw allow ${PORT}/tcp > /dev/null 2>&1; then echo -e "${GREEN}✓ ufw 规则已添加${NC}" FIREWALL_CONFIGURED=true else echo -e "${YELLOW}⚠ ufw 规则添加失败(可能需要手动配置)${NC}" fi fi fi # 检测 iptables(较老的系统) if command -v iptables > /dev/null 2>&1 && [ "$FIREWALL_CONFIGURED" = false ]; then # 检查是否有iptables规则(说明防火墙可能在使用) if sudo iptables -L -n 2>/dev/null | grep -q "Chain INPUT"; then echo -e "${BLUE}检测到 iptables,添加端口规则...${NC}" # 检查规则是否已存在 if ! sudo iptables -C INPUT -p tcp --dport ${PORT} -j ACCEPT > /dev/null 2>&1; then if sudo iptables -I INPUT -p tcp --dport ${PORT} -j ACCEPT > /dev/null 2>&1; then echo -e "${GREEN}✓ iptables 规则已添加${NC}" # 尝试保存规则(不同系统保存方式不同) if command -v iptables-save > /dev/null 2>&1; then if [ -f /etc/redhat-release ]; then sudo iptables-save > /etc/sysconfig/iptables 2>/dev/null || true elif [ -f /etc/debian_version ]; then sudo iptables-save > /etc/iptables/rules.v4 2>/dev/null || true fi fi FIREWALL_CONFIGURED=true else echo -e "${YELLOW}⚠ iptables 规则添加失败(可能需要手动配置)${NC}" fi else echo -e "${GREEN}✓ iptables 规则已存在${NC}" FIREWALL_CONFIGURED=true fi fi fi if [ "$FIREWALL_CONFIGURED" = false ]; then echo -e "${YELLOW}⚠ 未检测到活动的防火墙,或防火墙未启用${NC}" echo -e "${YELLOW} 如果节点无法远程访问,请手动开放端口 ${PORT}/tcp${NC}" fi } # 登记节点(调用心跳API获取节点信息) register_node() { echo -e "${BLUE}登记节点到后端服务器...${NC}" # 创建临时配置文件 CONFIG_FILE="$SOURCE_DIR/config.yaml" sudo mkdir -p "$SOURCE_DIR" # 创建基础配置文件 sudo tee "$CONFIG_FILE" > /dev/null <&1) if [ $? -eq 0 ]; then # 尝试解析JSON响应 NODE_ID=$(echo "$RESPONSE" | grep -o '"node_id":[0-9]*' | grep -o '[0-9]*' | head -1) NODE_IP=$(echo "$RESPONSE" | grep -o '"node_ip":"[^"]*"' | cut -d'"' -f4 | head -1) COUNTRY=$(echo "$RESPONSE" | grep -o '"country":"[^"]*"' | cut -d'"' -f4 | head -1) PROVINCE=$(echo "$RESPONSE" | grep -o '"province":"[^"]*"' | cut -d'"' -f4 | head -1) CITY=$(echo "$RESPONSE" | grep -o '"city":"[^"]*"' | cut -d'"' -f4 | head -1) ISP=$(echo "$RESPONSE" | grep -o '"isp":"[^"]*"' | cut -d'"' -f4 | head -1) if [ -n "$NODE_ID" ] && [ "$NODE_ID" != "0" ] && [ -n "$NODE_IP" ]; then # 更新配置文件 sudo tee "$CONFIG_FILE" > /dev/null < /dev/null 2>&1 sudo systemctl restart ${SERVICE_NAME} # 等待服务启动 sleep 3 # 检查服务状态 if sudo systemctl is-active --quiet ${SERVICE_NAME}; then echo -e "${GREEN}✓ 服务启动成功${NC}" else echo -e "${RED}✗ 服务启动失败${NC}" echo -e "${YELLOW}查看日志: sudo journalctl -u ${SERVICE_NAME} -n 50${NC}" exit 1 fi } # 验证安装 verify_installation() { echo -e "${BLUE}验证安装...${NC}" # 检查进程 if pgrep -f "$BINARY_NAME" > /dev/null; then echo -e "${GREEN}✓ 进程运行中${NC}" else echo -e "${YELLOW}⚠ 进程未运行${NC}" fi # 检查端口 if command -v netstat > /dev/null 2>&1; then if netstat -tlnp 2>/dev/null | grep -q ":2200"; then echo -e "${GREEN}✓ 端口 2200 已监听${NC}" fi elif command -v ss > /dev/null 2>&1; then if ss -tlnp 2>/dev/null | grep -q ":2200"; then echo -e "${GREEN}✓ 端口 2200 已监听${NC}" fi fi # 健康检查(重试多次,给服务启动时间) echo -e "${BLUE}等待服务启动并检查健康状态...${NC}" HEALTH_CHECK_PASSED=false for i in {1..10}; do sleep 2 if curl -sf http://localhost:2200/api/health > /dev/null 2>&1; then HEALTH_RESPONSE=$(curl -s http://localhost:2200/api/health 2>/dev/null || echo "") if echo "$HEALTH_RESPONSE" | grep -q '"status":"ok"'; then HEALTH_CHECK_PASSED=true echo -e "${GREEN}✓ 健康检查通过${NC}" break fi fi if [ $i -lt 10 ]; then echo -e "${BLUE}等待服务启动... ($i/10)${NC}" fi done if [ "$HEALTH_CHECK_PASSED" = false ]; then echo -e "${YELLOW}⚠ 健康检查未通过${NC}" echo -e "${YELLOW}请检查服务日志: sudo journalctl -u ${SERVICE_NAME} -n 50${NC}" echo -e "${YELLOW}或手动测试: curl http://localhost:2200/api/health${NC}" fi } # 主安装流程 main() { echo -e "${GREEN}========================================${NC}" echo -e "${GREEN} LinkMaster 节点端安装程序${NC}" echo -e "${GREEN}========================================${NC}" echo "" detect_system # 检查是否已安装,如果已安装则先卸载 if check_installed; then uninstall_service fi # 检测并配置最快的镜像源(在安装依赖之前) detect_fastest_mirror install_dependencies build_from_source create_service configure_firewall register_node start_service verify_installation echo "" echo -e "${GREEN}========================================${NC}" echo -e "${GREEN} 安装完成!${NC}" echo -e "${GREEN}========================================${NC}" echo "" echo -e "${BLUE}服务管理命令:${NC}" echo " 查看状态: sudo systemctl status ${SERVICE_NAME}" echo " 查看日志: sudo journalctl -u ${SERVICE_NAME} -f" echo " 重启服务: sudo systemctl restart ${SERVICE_NAME}" echo " 停止服务: sudo systemctl stop ${SERVICE_NAME}" echo "" echo -e "${BLUE}后端地址: ${BACKEND_URL}${NC}" echo -e "${BLUE}节点端口: 2200${NC}" echo "" echo -e "${YELLOW}重要提示:${NC}" echo " - 节点端直接连接后端服务器,不使用前端代理" echo " - 确保后端地址可访问: curl ${BACKEND_URL}/api/public/nodes/online" } # 执行安装 main