- 在 all-upload-release.sh 中添加临时打包目录,复制二进制文件及必要的脚本和配置文件。 - 修改 install.sh 以支持新格式发布包的提取,简化安装流程,无需从 Git 克隆。 - 更新 INSTALL.md 和 README.md,说明新格式发布包的优点和安装步骤。 - 确保安装脚本能够处理旧格式发布包,保持向后兼容性。
1581 lines
59 KiB
Bash
Executable File
1581 lines
59 KiB
Bash
Executable File
#!/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
|
||
|
||
# 错误处理函数
|
||
error_handler() {
|
||
local line_number=$1
|
||
local command=$2
|
||
echo ""
|
||
echo -e "${RED}========================================${NC}"
|
||
echo -e "${RED} 脚本执行出错!${NC}"
|
||
echo -e "${RED}========================================${NC}"
|
||
echo -e "${YELLOW}错误位置: 第 ${line_number} 行${NC}"
|
||
echo -e "${YELLOW}失败命令: ${command}${NC}"
|
||
echo ""
|
||
echo -e "${YELLOW}故障排查建议:${NC}"
|
||
echo " 1. 检查网络连接是否正常"
|
||
echo " 2. 检查后端地址是否正确: ${BACKEND_URL:-未设置}"
|
||
echo " 3. 检查是否有足够的磁盘空间和权限"
|
||
echo " 4. 查看上面的详细错误信息"
|
||
echo ""
|
||
echo -e "${YELLOW}查看服务日志: sudo journalctl -u ${SERVICE_NAME:-linkmaster-node} -n 50${NC}"
|
||
exit 1
|
||
}
|
||
|
||
# 设置错误陷阱
|
||
trap 'error_handler ${LINENO} "${BASH_COMMAND}"' ERR
|
||
|
||
# 颜色输出
|
||
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() {
|
||
# 检测操作系统类型(linux/darwin/windows)
|
||
OS_TYPE=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||
case $OS_TYPE in
|
||
linux)
|
||
OS_TYPE="linux"
|
||
if [ -f /etc/os-release ]; then
|
||
. /etc/os-release
|
||
OS=$ID
|
||
OS_VERSION=$VERSION_ID
|
||
else
|
||
OS="linux"
|
||
OS_VERSION=""
|
||
fi
|
||
;;
|
||
darwin)
|
||
OS_TYPE="darwin"
|
||
OS="darwin"
|
||
OS_VERSION=$(sw_vers -productVersion 2>/dev/null || echo "")
|
||
;;
|
||
*)
|
||
echo -e "${RED}不支持的操作系统: $OS_TYPE${NC}"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
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_TYPE $OS_VERSION ($ARCH)${NC}"
|
||
}
|
||
|
||
# 检测并选择最快的镜像源
|
||
detect_fastest_mirror() {
|
||
echo -e "${BLUE}检测最快的镜像源...${NC}"
|
||
|
||
# Ubuntu/Debian 镜像源列表
|
||
UBUNTU_MIRRORS=(
|
||
"mirrors.huaweicloud.com"
|
||
"mirrors.163.com"
|
||
"archive.ubuntu.com"
|
||
)
|
||
|
||
# CentOS/RHEL 镜像源列表
|
||
CENTOS_MIRRORS=(
|
||
"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 <<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
|
||
|
||
# 设置当前会话的 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
|
||
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
|
||
# 确保 PATH 包含 Go(如果从官网安装的)
|
||
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 [ -n "$GO_VERSION" ]; then
|
||
echo -e "${GREEN}✓ Go 已安装: ${GO_VERSION}${NC}"
|
||
# 再次验证 Go 是否可用
|
||
if go version > /dev/null 2>&1; then
|
||
echo -e "${BLUE}Go 环境正常,跳过安装流程${NC}"
|
||
return 0
|
||
else
|
||
echo -e "${YELLOW}⚠ Go 已安装但无法正常运行,尝试重新安装...${NC}"
|
||
fi
|
||
else
|
||
# 如果 command -v go 存在但无法获取版本,可能是 PATH 问题
|
||
# 尝试添加 /usr/local/go/bin 到 PATH
|
||
if [ -d "/usr/local/go/bin" ]; then
|
||
export PATH=/usr/local/go/bin:$PATH
|
||
GO_VERSION=$(go version 2>/dev/null | head -1 || echo "")
|
||
if [ -n "$GO_VERSION" ]; then
|
||
echo -e "${GREEN}✓ Go 已安装: ${GO_VERSION}${NC}"
|
||
echo -e "${BLUE}Go 环境正常,跳过安装流程${NC}"
|
||
return 0
|
||
fi
|
||
fi
|
||
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}"
|
||
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 ""
|
||
}
|
||
|
||
# 尝试从 Releases 下载二进制文件
|
||
download_binary_from_releases() {
|
||
echo -e "${BLUE}尝试从 Releases 下载预编译二进制文件...${NC}"
|
||
|
||
# Gitea API 地址
|
||
local api_base="https://gitee.nas.cpolar.cn/api/v1"
|
||
local repo_api="${api_base}/repos/${GITHUB_REPO}"
|
||
|
||
# 获取所有 releases(按创建时间排序,最新的在前)
|
||
echo -e "${BLUE}获取最新 release 信息...${NC}"
|
||
local releases_response=$(curl -s -X GET "${repo_api}/releases?limit=10" 2>/dev/null)
|
||
|
||
if [ $? -ne 0 ] || [ -z "$releases_response" ]; then
|
||
echo -e "${YELLOW}⚠ 无法获取 releases 信息,将使用源码编译${NC}"
|
||
return 1
|
||
fi
|
||
|
||
# 解析所有 releases,找到最新的(按创建时间或版本号)
|
||
# Gitea API 返回的 releases 通常已经按创建时间倒序排列
|
||
# 但我们还是需要解析并验证
|
||
|
||
# 提取所有 tag 和对应的 release_id、创建时间
|
||
local latest_tag=""
|
||
local latest_release_id=""
|
||
local latest_created_at=""
|
||
|
||
# 使用更健壮的方式解析 JSON(虽然简单,但能工作)
|
||
# 查找第一个有效的 release(非 draft,非 prerelease)
|
||
local tag_line=$(echo "$releases_response" | grep -o '"tag_name":"[^"]*"' | head -1)
|
||
local id_line=$(echo "$releases_response" | grep -o '"id":[0-9]*' | head -1)
|
||
local created_line=$(echo "$releases_response" | grep -o '"created_at":"[^"]*"' | head -1)
|
||
local draft_line=$(echo "$releases_response" | grep -o '"draft":[^,}]*' | head -1)
|
||
local prerelease_line=$(echo "$releases_response" | grep -o '"prerelease":[^,}]*' | head -1)
|
||
|
||
# 检查是否是 draft 或 prerelease
|
||
if echo "$draft_line" | grep -q "true" || echo "$prerelease_line" | grep -q "true"; then
|
||
# 如果是 draft 或 prerelease,尝试找下一个
|
||
echo -e "${YELLOW}⚠ 第一个 release 是草稿或预发布版本,查找正式版本...${NC}"
|
||
# 简化处理:如果第一个是预发布,仍然使用它(因为可能是最新的)
|
||
fi
|
||
|
||
latest_tag=$(echo "$tag_line" | cut -d'"' -f4)
|
||
latest_release_id=$(echo "$id_line" | cut -d':' -f2)
|
||
latest_created_at=$(echo "$created_line" | cut -d'"' -f4)
|
||
|
||
if [ -z "$latest_tag" ] || [ -z "$latest_release_id" ]; then
|
||
echo -e "${YELLOW}⚠ 无法解析 release 信息,将使用源码编译${NC}"
|
||
return 1
|
||
fi
|
||
|
||
# 显示找到的版本信息
|
||
echo -e "${GREEN}✓ 找到最新版本: ${latest_tag}${NC}"
|
||
if [ -n "$latest_created_at" ]; then
|
||
echo -e "${BLUE} 发布日期: ${latest_created_at}${NC}"
|
||
fi
|
||
|
||
# 获取 release 的详细信息(包含 commit hash)
|
||
local release_detail=$(curl -s -X GET "${repo_api}/releases/${latest_release_id}" 2>/dev/null)
|
||
|
||
if [ $? -ne 0 ] || [ -z "$release_detail" ]; then
|
||
echo -e "${YELLOW}⚠ 无法获取 release 详细信息,将使用源码编译${NC}"
|
||
return 1
|
||
fi
|
||
|
||
# 解析 release 对应的 commit hash(target_commitish)
|
||
local release_commit=$(echo "$release_detail" | grep -o '"target_commitish":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||
|
||
# 如果 target_commitish 是分支名(如 "main"),需要通过 API 获取该分支的 commit hash
|
||
if [ -n "$release_commit" ] && [ "${#release_commit}" -lt 40 ]; then
|
||
# 可能是分支名,尝试获取分支的最新 commit hash
|
||
local branch_info=$(curl -s -X GET "${repo_api}/git/commits/${release_commit}" 2>/dev/null)
|
||
if [ $? -eq 0 ] && [ -n "$branch_info" ]; then
|
||
local branch_commit=$(echo "$branch_info" | grep -o '"sha":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||
if [ -n "$branch_commit" ] && [ "${#branch_commit}" -eq 40 ]; then
|
||
release_commit="$branch_commit"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# 如果 release_detail 中没有 target_commitish 或获取失败,尝试通过 tag 获取 commit hash
|
||
if [ -z "$release_commit" ] || [ "${#release_commit}" -lt 40 ]; then
|
||
# 通过 tag API 获取 commit hash
|
||
local tag_info=$(curl -s -X GET "${repo_api}/git/tags/${latest_tag}" 2>/dev/null)
|
||
if [ $? -eq 0 ] && [ -n "$tag_info" ]; then
|
||
local tag_commit=$(echo "$tag_info" | grep -o '"sha":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||
if [ -n "$tag_commit" ] && [ "${#tag_commit}" -eq 40 ]; then
|
||
release_commit="$tag_commit"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# 构建文件名(根据系统类型)
|
||
# 处理 tag 可能带 v 前缀的情况(如 v1.0.0)
|
||
local version_in_filename="${latest_tag}"
|
||
|
||
# 如果 tag 以 v 开头,同时尝试带 v 和不带 v 的文件名
|
||
local file_ext="tar.gz"
|
||
if [ "$OS_TYPE" = "windows" ]; then
|
||
file_ext="zip"
|
||
fi
|
||
|
||
# 先尝试使用 tag 的原始格式(可能带 v)
|
||
local file_name_with_v="agent-${OS_TYPE}-${ARCH}-${latest_tag}"
|
||
local full_file_name_with_v="${file_name_with_v}.${file_ext}"
|
||
|
||
# 如果 tag 以 v 开头,也尝试不带 v 的版本
|
||
local file_name_without_v=""
|
||
local full_file_name_without_v=""
|
||
if [ "${latest_tag#v}" != "${latest_tag}" ]; then
|
||
# tag 以 v 开头,去掉 v 前缀
|
||
version_in_filename="${latest_tag#v}"
|
||
file_name_without_v="agent-${OS_TYPE}-${ARCH}-${version_in_filename}"
|
||
full_file_name_without_v="${file_name_without_v}.${file_ext}"
|
||
fi
|
||
|
||
# 查找匹配的二进制文件(优先尝试带 v 的,如果找不到再尝试不带 v 的)
|
||
local download_url=""
|
||
local full_file_name=""
|
||
|
||
# 先尝试带 v 的文件名
|
||
download_url=$(echo "$release_detail" | grep -o "\"browser_download_url\":\"[^\"]*${full_file_name_with_v}[^\"]*\"" | head -1 | cut -d'"' -f4)
|
||
|
||
if [ -n "$download_url" ]; then
|
||
full_file_name="$full_file_name_with_v"
|
||
echo -e "${BLUE}找到文件: ${full_file_name}${NC}"
|
||
elif [ -n "$full_file_name_without_v" ]; then
|
||
# 如果带 v 的找不到,尝试不带 v 的
|
||
download_url=$(echo "$release_detail" | grep -o "\"browser_download_url\":\"[^\"]*${full_file_name_without_v}[^\"]*\"" | head -1 | cut -d'"' -f4)
|
||
if [ -n "$download_url" ]; then
|
||
full_file_name="$full_file_name_without_v"
|
||
echo -e "${BLUE}找到文件: ${full_file_name} (tag: ${latest_tag})${NC}"
|
||
fi
|
||
fi
|
||
|
||
if [ -z "$download_url" ] || [ -z "$full_file_name" ]; then
|
||
echo -e "${YELLOW}⚠ 未找到匹配的二进制文件${NC}"
|
||
echo -e "${YELLOW} 尝试的文件名:${NC}"
|
||
echo -e "${YELLOW} - ${full_file_name_with_v}${NC}"
|
||
if [ -n "$full_file_name_without_v" ]; then
|
||
echo -e "${YELLOW} - ${full_file_name_without_v}${NC}"
|
||
fi
|
||
echo -e "${YELLOW} 将使用源码编译${NC}"
|
||
return 1
|
||
fi
|
||
|
||
echo -e "${BLUE}下载二进制文件: ${full_file_name}...${NC}"
|
||
|
||
# 创建临时目录
|
||
local temp_dir=$(mktemp -d)
|
||
local download_file="${temp_dir}/${full_file_name}"
|
||
|
||
# 下载文件
|
||
if ! curl -fsSL -o "$download_file" "$download_url" 2>/dev/null; then
|
||
echo -e "${YELLOW}⚠ 下载失败,将使用源码编译${NC}"
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
fi
|
||
|
||
# 检查文件大小(至少应该大于 1MB)
|
||
local file_size=$(stat -f%z "$download_file" 2>/dev/null || stat -c%s "$download_file" 2>/dev/null || echo "0")
|
||
if [ "$file_size" -lt 1048576 ]; then
|
||
echo -e "${YELLOW}⚠ 下载的文件大小异常,将使用源码编译${NC}"
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
fi
|
||
|
||
# 解压文件
|
||
echo -e "${BLUE}解压发布包...${NC}"
|
||
cd "$temp_dir"
|
||
|
||
local extracted_dir=""
|
||
if [ "$file_ext" = "tar.gz" ]; then
|
||
if ! tar -xzf "$download_file" 2>/dev/null; then
|
||
echo -e "${YELLOW}⚠ 解压失败,将使用源码编译${NC}"
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
fi
|
||
# 查找解压后的目录(可能是直接解压到当前目录,也可能是在子目录中)
|
||
extracted_dir=$(find . -maxdepth 1 -type d ! -name "." ! -name ".." | head -1)
|
||
if [ -z "$extracted_dir" ]; then
|
||
extracted_dir="."
|
||
fi
|
||
else
|
||
# Windows zip 文件(虽然脚本主要在 Linux 上运行,但保留兼容性)
|
||
if ! unzip -q "$download_file" 2>/dev/null; then
|
||
echo -e "${YELLOW}⚠ 解压失败,将使用源码编译${NC}"
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
fi
|
||
extracted_dir=$(find . -maxdepth 1 -type d ! -name "." ! -name ".." | head -1)
|
||
if [ -z "$extracted_dir" ]; then
|
||
extracted_dir="."
|
||
fi
|
||
fi
|
||
|
||
cd "$extracted_dir" || {
|
||
echo -e "${YELLOW}⚠ 无法进入解压目录,将使用源码编译${NC}"
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
}
|
||
|
||
# 显示解压后的目录内容(用于调试)
|
||
echo -e "${BLUE}解压目录内容:${NC}"
|
||
ls -la . 2>/dev/null || true
|
||
echo ""
|
||
|
||
# 查找二进制文件(先检查当前目录,再递归查找)
|
||
local binary_file=""
|
||
if [ "$OS_TYPE" = "windows" ]; then
|
||
if [ -f "./agent.exe" ]; then
|
||
binary_file="./agent.exe"
|
||
elif [ -f "agent.exe" ]; then
|
||
binary_file="agent.exe"
|
||
else
|
||
# 递归查找所有 .exe 文件
|
||
binary_file=$(find . -type f -name "*.exe" ! -name "*.tar.gz" ! -name "*.zip" 2>/dev/null | grep -i agent | head -1)
|
||
fi
|
||
else
|
||
# Linux/macOS: 先检查常见位置
|
||
if [ -f "./agent" ]; then
|
||
binary_file="./agent"
|
||
elif [ -f "agent" ]; then
|
||
binary_file="agent"
|
||
else
|
||
# 递归查找所有文件,排除压缩包和目录
|
||
# 查找名为 agent 的文件(不是目录)
|
||
binary_file=$(find . -type f \( -name "agent" -o -name "agent-*" \) ! -name "*.tar.gz" ! -name "*.zip" 2>/dev/null | head -1)
|
||
|
||
# 如果还是找不到,尝试查找所有可执行文件
|
||
if [ -z "$binary_file" ]; then
|
||
binary_file=$(find . -type f -perm +111 ! -name "*.tar.gz" ! -name "*.zip" ! -name "*.sh" 2>/dev/null | head -1)
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
if [ -z "$binary_file" ] || [ ! -f "$binary_file" ]; then
|
||
echo -e "${YELLOW}⚠ 未找到解压后的二进制文件,将使用源码编译${NC}"
|
||
echo -e "${YELLOW} 当前目录: $(pwd)${NC}"
|
||
echo -e "${YELLOW} 查找的文件: agent 或 agent-*${NC}"
|
||
echo -e "${YELLOW} 所有文件列表:${NC}"
|
||
find . -type f 2>/dev/null | head -20 || true
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
fi
|
||
|
||
# 确保使用绝对路径
|
||
local binary_path=""
|
||
if [[ "$binary_file" == /* ]]; then
|
||
binary_path="$binary_file"
|
||
else
|
||
# 转换为绝对路径
|
||
binary_path="$(cd "$(dirname "$binary_file")" && pwd)/$(basename "$binary_file")"
|
||
fi
|
||
|
||
echo -e "${GREEN}✓ 找到二进制文件: ${binary_path}${NC}"
|
||
|
||
# 验证二进制文件是否可执行
|
||
if [ ! -x "$binary_path" ]; then
|
||
chmod +x "$binary_path" 2>/dev/null || true
|
||
fi
|
||
|
||
# 验证二进制文件类型(Linux 应该是 ELF 文件)
|
||
if [ "$OS_TYPE" = "linux" ]; then
|
||
if command -v file > /dev/null 2>&1; then
|
||
local file_type=$(file "$binary_path" 2>/dev/null || echo "")
|
||
if [ -n "$file_type" ] && ! echo "$file_type" | grep -qi "ELF"; then
|
||
echo -e "${YELLOW}⚠ 二进制文件类型异常: ${file_type}${NC}"
|
||
echo -e "${YELLOW} 将使用源码编译${NC}"
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# 保存当前目录(extracted_dir)
|
||
local extracted_path="$(pwd)"
|
||
|
||
# 检查是否是新格式的发布包(包含脚本文件)
|
||
local has_scripts=false
|
||
if [ -f "$extracted_path/install.sh" ] || [ -f "$extracted_path/run.sh" ] || [ -f "$extracted_path/start-systemd.sh" ]; then
|
||
has_scripts=true
|
||
echo -e "${GREEN}✓ 检测到新格式发布包(包含脚本文件)${NC}"
|
||
fi
|
||
|
||
# 创建源码目录
|
||
if [ -d "$SOURCE_DIR" ]; then
|
||
sudo rm -rf "$SOURCE_DIR"
|
||
fi
|
||
sudo mkdir -p "$SOURCE_DIR"
|
||
|
||
if [ "$has_scripts" = true ]; then
|
||
# 新格式:从压缩包提取所有文件
|
||
echo -e "${BLUE}从发布包提取所有文件...${NC}"
|
||
|
||
# 复制二进制文件
|
||
sudo cp "$binary_path" "$SOURCE_DIR/agent"
|
||
sudo chmod +x "$SOURCE_DIR/agent"
|
||
echo -e "${GREEN}✓ 已提取二进制文件${NC}"
|
||
|
||
# 复制脚本文件
|
||
local scripts=("install.sh" "run.sh" "start-systemd.sh" "uninstall.sh")
|
||
for script in "${scripts[@]}"; do
|
||
if [ -f "$extracted_path/$script" ]; then
|
||
sudo cp "$extracted_path/$script" "$SOURCE_DIR/"
|
||
sudo chmod +x "$SOURCE_DIR/$script"
|
||
echo -e "${GREEN}✓ 已提取 $script${NC}"
|
||
fi
|
||
done
|
||
|
||
# 复制示例配置文件
|
||
if [ -f "$extracted_path/config.yaml.example" ]; then
|
||
sudo cp "$extracted_path/config.yaml.example" "$SOURCE_DIR/config.yaml.example"
|
||
echo -e "${GREEN}✓ 已提取 config.yaml.example${NC}"
|
||
fi
|
||
|
||
echo -e "${GREEN}✓ 所有文件已从发布包提取,无需克隆 Git 仓库${NC}"
|
||
else
|
||
# 旧格式:只有二进制文件,需要克隆 Git 仓库获取脚本
|
||
echo -e "${BLUE}检测到旧格式发布包(仅包含二进制文件)${NC}"
|
||
echo -e "${BLUE}克隆仓库以获取启动脚本...${NC}"
|
||
|
||
if ! sudo git clone --branch "${GITHUB_BRANCH}" "https://gitee.nas.cpolar.cn/${GITHUB_REPO}.git" "$SOURCE_DIR" 2>&1; then
|
||
echo -e "${YELLOW}⚠ 克隆仓库失败,将使用源码编译${NC}"
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
fi
|
||
|
||
# 对比 Git commit hash(仅旧格式需要)
|
||
if [ -n "$release_commit" ]; then
|
||
echo -e "${BLUE}验证 Git commit 版本...${NC}"
|
||
|
||
if [ -d "$SOURCE_DIR/.git" ]; then
|
||
cd "$SOURCE_DIR" || {
|
||
echo -e "${YELLOW}⚠ 无法切换到源码目录,跳过验证${NC}"
|
||
cd /tmp || true
|
||
}
|
||
|
||
local current_commit=$(git rev-parse HEAD 2>/dev/null || echo "")
|
||
|
||
if [ -n "$current_commit" ] && [ "${#release_commit}" -eq 40 ] && [ "${#current_commit}" -eq 40 ]; then
|
||
local release_commit_short=$(echo "$release_commit" | cut -c1-7)
|
||
local current_commit_short=$(echo "$current_commit" | cut -c1-7)
|
||
|
||
echo -e "${BLUE} Release commit: ${release_commit_short}${NC}"
|
||
echo -e "${BLUE} 当前代码 commit: ${current_commit_short}${NC}"
|
||
|
||
if [ "$release_commit" != "$current_commit" ]; then
|
||
echo -e "${YELLOW}⚠ Commit hash 不匹配,二进制文件可能不是最新代码编译的${NC}"
|
||
echo -e "${YELLOW} Release 基于较旧的代码,将使用源码编译最新版本${NC}"
|
||
cd /tmp || true
|
||
rm -rf "$temp_dir"
|
||
return 1
|
||
else
|
||
echo -e "${GREEN}✓ Commit hash 匹配${NC}"
|
||
fi
|
||
fi
|
||
|
||
cd /tmp || true
|
||
fi
|
||
fi
|
||
|
||
# 用下载的二进制文件覆盖克隆目录中的文件
|
||
sudo cp "$binary_path" "$SOURCE_DIR/agent"
|
||
sudo chmod +x "$SOURCE_DIR/agent"
|
||
fi
|
||
|
||
# 复制到安装目录
|
||
sudo mkdir -p "$INSTALL_DIR"
|
||
sudo cp "$SOURCE_DIR/agent" "$INSTALL_DIR/$BINARY_NAME"
|
||
sudo chmod +x "$INSTALL_DIR/$BINARY_NAME"
|
||
|
||
# 清理临时文件
|
||
rm -rf "$temp_dir"
|
||
|
||
# 显示文件信息
|
||
local binary_size=$(du -h "$SOURCE_DIR/agent" | cut -f1)
|
||
echo -e "${GREEN}✓ 安装文件准备完成 (文件大小: ${binary_size})${NC}"
|
||
echo -e "${BLUE}版本: ${latest_tag}${NC}"
|
||
echo -e "${BLUE}安装目录: ${SOURCE_DIR}${NC}"
|
||
|
||
return 0
|
||
}
|
||
|
||
# 从源码编译安装
|
||
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 已安装,先确保 PATH 正确
|
||
if [ -d "/usr/local/go/bin" ] && ! echo "$PATH" | grep -q "/usr/local/go/bin"; then
|
||
export PATH=/usr/local/go/bin:$PATH
|
||
fi
|
||
|
||
# 验证是否可用
|
||
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
|
||
|
||
# 检查源码目录是否已存在(可能由 download_binary_from_releases 已克隆)
|
||
if [ -d "$SOURCE_DIR" ] && [ -d "$SOURCE_DIR/.git" ]; then
|
||
echo -e "${BLUE}检测到已存在的仓库目录,复用现有目录...${NC}"
|
||
cd "$SOURCE_DIR"
|
||
# 确保是最新代码
|
||
echo -e "${BLUE}更新代码到最新版本...${NC}"
|
||
sudo git fetch origin 2>/dev/null || true
|
||
sudo git checkout "${GITHUB_BRANCH}" 2>/dev/null || true
|
||
sudo git pull origin "${GITHUB_BRANCH}" 2>/dev/null || true
|
||
else
|
||
# 如果源码目录已存在但不是 git 仓库,删除它
|
||
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
|
||
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
|
||
|
||
# 检查 vendor 目录是否存在(必须存在)
|
||
echo -e "${BLUE}检查 vendor 目录...${NC}"
|
||
if [ ! -d "$SOURCE_DIR/vendor" ] || [ ! -f "$SOURCE_DIR/vendor/modules.txt" ]; then
|
||
echo -e "${RED}错误: vendor 目录不存在或无效${NC}"
|
||
echo -e "${RED}请确保项目包含 vendor 目录,或先运行 ./vendor.sh 创建 vendor 目录${NC}"
|
||
echo -e "${YELLOW}vendor 目录路径: ${SOURCE_DIR}/vendor${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
# vendor 目录存在,显示信息
|
||
VENDOR_COUNT=$(find "$SOURCE_DIR/vendor" -type d -mindepth 2 2>/dev/null | wc -l | tr -d '\n' || echo "0")
|
||
VENDOR_COUNT=${VENDOR_COUNT:-0}
|
||
echo -e "${GREEN}✓ vendor 目录存在${NC}"
|
||
echo -e "${BLUE}vendor 目录包含 ${GREEN}${VENDOR_COUNT}${NC} 个依赖包"
|
||
echo -e "${BLUE}编译时将使用 -mod=vendor 标志,无需网络连接${NC}"
|
||
|
||
# 构建包含 Go PATH 的环境变量
|
||
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
|
||
|
||
# 设置 Go 环境变量(仅包含 PATH,不使用代理)
|
||
GO_ENV="$GO_PATH_ENV"
|
||
|
||
# 编译到临时文件(在用户有权限的目录),然后移动到目标位置
|
||
echo -e "${BLUE}编译二进制文件...${NC}"
|
||
TEMP_BINARY=$(mktemp)
|
||
BINARY_PATH="$SOURCE_DIR/agent"
|
||
|
||
# 使用 sudo 以 root 用户编译,直接输出到目标位置,强制使用 vendor
|
||
echo -e "${BLUE}开始编译(架构: ${ARCH})...${NC}"
|
||
echo -e "${BLUE}使用 vendor 目录编译(无需网络连接)...${NC}"
|
||
|
||
# 强制使用 vendor 模式编译
|
||
BUILD_FLAGS="-mod=vendor -v -buildvcs=false -ldflags='-w -s'"
|
||
|
||
if sudo bash -c "cd '$SOURCE_DIR' && $GO_ENV && GOOS=linux GOARCH=${ARCH} CGO_ENABLED=0 go build $BUILD_FLAGS -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"
|
||
|
||
# 检测 Go 的安装路径
|
||
GO_PATH=""
|
||
if [ -d "/usr/local/go/bin" ]; then
|
||
GO_PATH="/usr/local/go/bin"
|
||
elif command -v go > /dev/null 2>&1; then
|
||
GO_BIN=$(command -v go)
|
||
GO_PATH=$(dirname "$GO_BIN")
|
||
fi
|
||
|
||
# 构建 PATH 环境变量
|
||
if [ -n "$GO_PATH" ]; then
|
||
ENV_PATH="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${GO_PATH}"
|
||
else
|
||
ENV_PATH="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||
fi
|
||
|
||
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"
|
||
Environment="$ENV_PATH"
|
||
|
||
[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}"
|
||
echo -e "${BLUE}后端地址: ${BACKEND_URL}${NC}"
|
||
|
||
# 添加超时设置,避免长时间卡住
|
||
# 使用 set +e 临时禁用错误退出,因为心跳失败不应该阻止安装
|
||
set +e
|
||
RESPONSE=$(curl -s --connect-timeout 10 --max-time 30 -X POST "${BACKEND_URL}/api/node/heartbeat" \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "type=pingServer" 2>&1)
|
||
CURL_EXIT_CODE=$?
|
||
set -e # 重新启用错误退出
|
||
|
||
if [ $CURL_EXIT_CODE -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}⚠ 心跳请求失败 (退出码: ${CURL_EXIT_CODE}),将在服务启动时重试${NC}"
|
||
echo -e "${YELLOW} 错误信息: ${RESPONSE}${NC}"
|
||
echo -e "${YELLOW} 提示: 请检查后端地址是否正确: ${BACKEND_URL}${NC}"
|
||
echo -e "${YELLOW} 测试连接: curl -v ${BACKEND_URL}/api/public/nodes/online${NC}"
|
||
fi
|
||
|
||
# 设置配置文件权限
|
||
sudo chmod 644 "$CONFIG_FILE"
|
||
}
|
||
|
||
# 启动服务
|
||
start_service() {
|
||
echo -e "${BLUE}启动服务...${NC}"
|
||
|
||
# 先检查服务文件是否存在
|
||
if [ ! -f "/etc/systemd/system/${SERVICE_NAME}.service" ]; then
|
||
echo -e "${RED}✗ 错误: 服务文件不存在${NC}"
|
||
echo -e "${YELLOW} 路径: /etc/systemd/system/${SERVICE_NAME}.service${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
# 检查二进制文件是否存在
|
||
if [ ! -f "$SOURCE_DIR/agent" ] && [ ! -f "$INSTALL_DIR/$BINARY_NAME" ]; then
|
||
echo -e "${RED}✗ 错误: 二进制文件不存在${NC}"
|
||
echo -e "${YELLOW} 检查路径: $SOURCE_DIR/agent 或 $INSTALL_DIR/$BINARY_NAME${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
# 启用服务(显示输出以便调试)
|
||
echo -e "${BLUE}启用服务...${NC}"
|
||
if ! sudo systemctl enable ${SERVICE_NAME} 2>&1; then
|
||
echo -e "${RED}✗ 启用服务失败${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
# 重新加载 systemd
|
||
echo -e "${BLUE}重新加载 systemd...${NC}"
|
||
sudo systemctl daemon-reload
|
||
|
||
# 启动服务(显示输出以便调试)
|
||
echo -e "${BLUE}启动服务...${NC}"
|
||
if ! sudo systemctl restart ${SERVICE_NAME} 2>&1; then
|
||
echo -e "${RED}✗ 启动服务失败${NC}"
|
||
echo -e "${YELLOW}查看详细日志: sudo journalctl -u ${SERVICE_NAME} -n 100 --no-pager${NC}"
|
||
echo -e "${YELLOW}查看服务状态: sudo systemctl status ${SERVICE_NAME}${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
# 等待服务启动
|
||
echo -e "${BLUE}等待服务启动...${NC}"
|
||
sleep 3
|
||
|
||
# 检查服务状态
|
||
if sudo systemctl is-active --quiet ${SERVICE_NAME} 2>/dev/null; then
|
||
echo -e "${GREEN}✓ 服务启动成功${NC}"
|
||
else
|
||
echo -e "${RED}✗ 服务启动失败${NC}"
|
||
echo -e "${YELLOW}服务状态:${NC}"
|
||
sudo systemctl status ${SERVICE_NAME} --no-pager -l || true
|
||
echo -e "${YELLOW}查看详细日志: sudo journalctl -u ${SERVICE_NAME} -n 100 --no-pager${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 ""
|
||
echo -e "${BLUE}后端地址: ${BACKEND_URL}${NC}"
|
||
echo ""
|
||
|
||
echo -e "${BLUE}[1/8] 检测系统类型...${NC}"
|
||
detect_system
|
||
|
||
# 检查是否已安装,如果已安装则先卸载
|
||
if check_installed; then
|
||
echo -e "${BLUE}[2/8] 卸载已存在的服务...${NC}"
|
||
uninstall_service
|
||
else
|
||
echo -e "${BLUE}[2/8] 检查已安装服务...${NC}"
|
||
echo -e "${GREEN}✓ 未检测到已安装的服务${NC}"
|
||
fi
|
||
|
||
echo -e "${BLUE}[3/8] 检测并配置镜像源...${NC}"
|
||
detect_fastest_mirror
|
||
|
||
echo -e "${BLUE}[4/8] 安装系统依赖...${NC}"
|
||
install_dependencies
|
||
|
||
# 优先尝试从 Releases 下载二进制文件
|
||
echo -e "${BLUE}[5/8] 下载或编译二进制文件...${NC}"
|
||
if ! download_binary_from_releases; then
|
||
echo -e "${BLUE}从 Releases 下载失败,开始从源码编译...${NC}"
|
||
build_from_source
|
||
else
|
||
echo -e "${GREEN}✓ 使用预编译二进制文件,跳过编译步骤${NC}"
|
||
fi
|
||
|
||
echo -e "${BLUE}[6/8] 创建 systemd 服务...${NC}"
|
||
create_service
|
||
|
||
echo -e "${BLUE}[7/8] 配置防火墙规则...${NC}"
|
||
configure_firewall
|
||
|
||
echo -e "${BLUE}[8/8] 登记节点到后端服务器...${NC}"
|
||
register_node
|
||
|
||
echo -e "${BLUE}[9/9] 启动服务...${NC}"
|
||
start_service
|
||
|
||
echo -e "${BLUE}[10/10] 验证安装...${NC}"
|
||
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
|