865 lines
30 KiB
Bash
Executable File
865 lines
30 KiB
Bash
Executable File
#!/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.aliyun.com"
|
||
"mirrors.tuna.tsinghua.edu.cn"
|
||
"mirrors.ustc.edu.cn"
|
||
"mirrors.huaweicloud.com"
|
||
"mirrors.163.com"
|
||
"archive.ubuntu.com"
|
||
)
|
||
|
||
# CentOS/RHEL 镜像源列表
|
||
CENTOS_MIRRORS=(
|
||
"mirrors.aliyun.com"
|
||
"mirrors.tuna.tsinghua.edu.cn"
|
||
"mirrors.ustc.edu.cn"
|
||
"mirrors.huaweicloud.com"
|
||
"mirrorlist.centos.org"
|
||
)
|
||
|
||
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 <<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
|
||
sudo apt-get update -qq
|
||
sudo apt-get install -y -qq curl wget ping traceroute dnsutils git > /dev/null 2>&1
|
||
elif [ "$OS" = "centos" ] || [ "$OS" = "rhel" ] || [ "$OS" = "rocky" ] || [ "$OS" = "almalinux" ]; then
|
||
sudo yum install -y -q curl wget iputils traceroute bind-utils git > /dev/null 2>&1
|
||
else
|
||
echo -e "${YELLOW}警告: 未知系统类型,跳过依赖安装${NC}"
|
||
fi
|
||
|
||
echo -e "${GREEN}✓ 依赖安装完成${NC}"
|
||
}
|
||
|
||
# 安装 Go 环境
|
||
install_go() {
|
||
echo -e "${BLUE}安装 Go 环境...${NC}"
|
||
|
||
if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ]; then
|
||
sudo apt-get update -qq
|
||
sudo apt-get install -y -qq golang-go > /dev/null 2>&1
|
||
elif [ "$OS" = "centos" ] || [ "$OS" = "rhel" ] || [ "$OS" = "rocky" ] || [ "$OS" = "almalinux" ]; then
|
||
sudo yum install -y -q golang > /dev/null 2>&1
|
||
else
|
||
echo -e "${YELLOW}无法自动安装 Go,请手动安装: https://golang.org/dl/${NC}"
|
||
show_build_alternatives
|
||
exit 1
|
||
fi
|
||
|
||
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}"
|
||
else
|
||
echo -e "${RED}Go 安装失败${NC}"
|
||
show_build_alternatives
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# 显示替代方案
|
||
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
|
||
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}"
|
||
|
||
# 如果源码目录已存在,删除(卸载函数应该已经删除,这里作为保险)
|
||
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
|
||
|
||
# 下载依赖(使用 sudo 以 root 用户执行)
|
||
echo -e "${BLUE}下载 Go 依赖...${NC}"
|
||
if ! sudo bash -c "cd '$SOURCE_DIR' && go mod download" 2>&1; then
|
||
echo -e "${RED}下载依赖失败${NC}"
|
||
show_build_alternatives
|
||
exit 1
|
||
fi
|
||
|
||
# 编译到临时文件(在用户有权限的目录),然后移动到目标位置
|
||
echo -e "${BLUE}编译二进制文件...${NC}"
|
||
TEMP_BINARY=$(mktemp)
|
||
BINARY_PATH="$SOURCE_DIR/agent"
|
||
|
||
# 使用 sudo 以 root 用户编译,直接输出到目标位置
|
||
if sudo bash -c "cd '$SOURCE_DIR' && GOOS=linux GOARCH=${ARCH} CGO_ENABLED=0 go build -buildvcs=false -ldflags='-w -s' -o '$BINARY_PATH' ./cmd/agent" 2>&1; then
|
||
if [ -f "$BINARY_PATH" ] && [ -s "$BINARY_PATH" ]; then
|
||
sudo chmod +x "$BINARY_PATH"
|
||
echo -e "${GREEN}✓ 编译成功${NC}"
|
||
else
|
||
echo -e "${RED}编译失败:未生成二进制文件${NC}"
|
||
show_build_alternatives
|
||
exit 1
|
||
fi
|
||
else
|
||
echo -e "${RED}编译失败${NC}"
|
||
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
|