Files
linkmaster-node/install.sh
2025-12-03 18:24:48 +08:00

1395 lines
54 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================
# LinkMaster 节点端一键安装脚本
# 使用方法: curl -fsSL https://gitee.cpolar.top/yoyo/linkmaster-node/raw/branch/main/install.sh | bash -s -- <后端地址>
# 示例: curl -fsSL https://gitee.cpolar.top/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.cpolar.top/${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 使用 -WLinux 使用 -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 <<EOF
# LinkMaster Node 自动配置的镜像源
deb https://${mirror}/ubuntu/ ${CODENAME} main restricted universe multiverse
deb https://${mirror}/ubuntu/ ${CODENAME}-updates main restricted universe multiverse
deb https://${mirror}/ubuntu/ ${CODENAME}-backports main restricted universe multiverse
deb https://${mirror}/ubuntu/ ${CODENAME}-security main restricted universe multiverse
EOF
elif [ "$OS" = "debian" ]; then
# Debian 版本检测
if [ -f /etc/debian_version ]; then
DEBIAN_VERSION=$(cat /etc/debian_version | cut -d'.' -f1)
case "$DEBIAN_VERSION" in
"12") CODENAME="bookworm" ;;
"11") CODENAME="bullseye" ;;
"10") CODENAME="buster" ;;
*) CODENAME="bookworm" ;;
esac
else
CODENAME="bookworm"
fi
echo -e "${BLUE}配置 Debian 镜像源: ${mirror}${NC}"
sudo tee "$sources_file" > /dev/null <<EOF
# LinkMaster Node 自动配置的镜像源
deb https://${mirror}/debian/ ${CODENAME} main contrib non-free
deb https://${mirror}/debian/ ${CODENAME}-updates main contrib non-free
deb https://${mirror}/debian-security ${CODENAME}-security main contrib non-free
EOF
fi
echo -e "${GREEN}✓ 镜像源配置完成${NC}"
}
# 配置 CentOS/RHEL 镜像源
configure_centos_mirror() {
local mirror="$1"
local repo_dir="/etc/yum.repos.d"
local backup_dir="/etc/yum.repos.d/backup.$(date +%Y%m%d_%H%M%S)"
# 备份原始源配置
if [ -d "$repo_dir" ] && [ ! -d "$backup_dir" ]; then
echo -e "${BLUE}备份原始源配置...${NC}"
sudo mkdir -p "$backup_dir"
sudo cp -r "$repo_dir"/*.repo "$backup_dir/" 2>/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 <<EOF
[baseos]
name=Rocky Linux \$releasever - BaseOS - ${mirror}
baseurl=https://${mirror}/rocky/\$releasever/BaseOS/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rocky
[appstream]
name=Rocky Linux \$releasever - AppStream - ${mirror}
baseurl=https://${mirror}/rocky/\$releasever/AppStream/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rocky
EOF
elif [ "$os_name" = "almalinux" ]; then
# AlmaLinux
sudo tee "$repo_dir/AlmaLinux-Base.repo" > /dev/null <<EOF
[baseos]
name=AlmaLinux \$releasever - BaseOS - ${mirror}
baseurl=https://${mirror}/almalinux/\$releasever/BaseOS/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux
[appstream]
name=AlmaLinux \$releasever - AppStream - ${mirror}
baseurl=https://${mirror}/almalinux/\$releasever/AppStream/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux
EOF
elif [ "$os_version" = "7" ]; then
# CentOS 7
sudo tee "$repo_dir/CentOS-Base.repo" > /dev/null <<EOF
[base]
name=CentOS-\$releasever - Base - ${mirror}
baseurl=https://${mirror}/centos/\$releasever/os/\$basearch/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[updates]
name=CentOS-\$releasever - Updates - ${mirror}
baseurl=https://${mirror}/centos/\$releasever/updates/\$basearch/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[extras]
name=CentOS-\$releasever - Extras - ${mirror}
baseurl=https://${mirror}/centos/\$releasever/extras/\$basearch/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
EOF
else
# CentOS Stream 8/9 或其他版本
sudo tee "$repo_dir/CentOS-Base.repo" > /dev/null <<EOF
[baseos]
name=CentOS-\$releasever - BaseOS - ${mirror}
baseurl=https://${mirror}/centos-stream/\$stream/BaseOS/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
[appstream]
name=CentOS-\$releasever - AppStream - ${mirror}
baseurl=https://${mirror}/centos-stream/\$stream/AppStream/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
EOF
fi
echo -e "${GREEN}✓ 镜像源配置完成${NC}"
}
# 安装系统依赖
install_dependencies() {
echo -e "${BLUE}安装系统依赖...${NC}"
if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ]; then
echo -e "${BLUE}更新 apt 软件包列表...${NC}"
sudo apt-get update
echo -e "${BLUE}安装系统依赖包...${NC}"
sudo apt-get install -y curl wget ping traceroute dnsutils git
elif [ "$OS" = "centos" ] || [ "$OS" = "rhel" ] || [ "$OS" = "rocky" ] || [ "$OS" = "almalinux" ]; then
echo -e "${BLUE}安装系统依赖包...${NC}"
sudo yum install -y curl wget iputils traceroute bind-utils git
else
echo -e "${YELLOW}警告: 未知系统类型,跳过依赖安装${NC}"
fi
echo -e "${GREEN}✓ 系统依赖安装完成${NC}"
}
# 从官网下载安装 Go
install_go_from_official() {
echo -e "${BLUE}从 Go 官网下载安装...${NC}"
# 检测架构
local arch=""
case "$ARCH" in
amd64)
arch="amd64"
;;
arm64)
arch="arm64"
;;
*)
echo -e "${RED}不支持的架构: $ARCH${NC}"
return 1
;;
esac
# Go 版本(可以根据需要修改)
local go_version="1.21.5"
local go_tar="go${go_version}.linux-${arch}.tar.gz"
local go_url="https://golang.google.cn/dl/${go_tar}"
# 如果国内镜像访问失败,尝试官方源
local download_success=false
# 尝试从国内镜像下载
echo -e "${BLUE}尝试从 Go 国内镜像下载...${NC}"
if curl -fsSL -o "/tmp/${go_tar}" "${go_url}" 2>/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.cpolar.top/${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.cpolar.top/${GITHUB_REPO}.git" "$SOURCE_DIR" 2>&1; then
echo -e "${RED}克隆仓库失败,请检查网络连接和仓库地址${NC}"
echo -e "${YELLOW}仓库地址: https://gitee.cpolar.top/${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
# 配置 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=300 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=300 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 GOPRIVATE=*" 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 标志下载,显示详细输出,同时写入日志和管道
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}.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_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=300"
# 重新启动进度显示
(
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 <<EOF
[Unit]
Description=LinkMaster Node Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$SOURCE_DIR
ExecStart=$SOURCE_DIR/start-systemd.sh
Restart=always
RestartSec=5
Environment="BACKEND_URL=$BACKEND_URL"
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
echo -e "${GREEN}✓ 服务创建完成${NC}"
}
# 配置防火墙规则
configure_firewall() {
echo -e "${BLUE}配置防火墙规则开放2200端口...${NC}"
PORT=2200
FIREWALL_CONFIGURED=false
# 检测 firewalld (CentOS/RHEL 7+, Fedora)
if command -v firewall-cmd > /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 <<EOF
server:
port: 2200
backend:
url: ${BACKEND_URL}
heartbeat:
interval: 60
debug: false
node:
id: 0
ip: ""
country: ""
province: ""
city: ""
isp: ""
EOF
# 调用心跳API获取节点信息
echo -e "${BLUE}发送心跳请求获取节点信息...${NC}"
RESPONSE=$(curl -s -X POST "${BACKEND_URL}/api/node/heartbeat" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "type=pingServer" 2>&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 <<EOF
server:
port: 2200
backend:
url: ${BACKEND_URL}
heartbeat:
interval: 60
debug: false
node:
id: ${NODE_ID}
ip: ${NODE_IP}
country: ${COUNTRY:-""}
province: ${PROVINCE:-""}
city: ${CITY:-""}
isp: ${ISP:-""}
EOF
echo -e "${GREEN}✓ 节点登记成功${NC}"
echo -e "${BLUE} 节点ID: ${NODE_ID}${NC}"
echo -e "${BLUE} 节点IP: ${NODE_IP}${NC}"
if [ -n "$COUNTRY" ] || [ -n "$PROVINCE" ] || [ -n "$CITY" ]; then
echo -e "${BLUE} 位置: ${COUNTRY:-""}/${PROVINCE:-""}/${CITY:-""}${NC}"
fi
else
echo -e "${YELLOW}⚠ 无法从响应中解析节点信息,将在服务启动时重试${NC}"
echo -e "${YELLOW} 响应: ${RESPONSE}${NC}"
fi
else
echo -e "${YELLOW}⚠ 心跳请求失败,将在服务启动时重试${NC}"
echo -e "${YELLOW} 错误: ${RESPONSE}${NC}"
fi
# 设置配置文件权限
sudo chmod 644 "$CONFIG_FILE"
}
# 启动服务
start_service() {
echo -e "${BLUE}启动服务...${NC}"
sudo systemctl enable ${SERVICE_NAME} > /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