Files
linkmaster-node/install.sh
2025-11-22 22:23:02 +08:00

499 lines
17 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://raw.githubusercontent.com/yourbask/linkmaster-node/main/install.sh | bash -s -- <后端地址>
# 示例: curl -fsSL https://raw.githubusercontent.com/yourbask/linkmaster-node/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:-yourbask/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://raw.githubusercontent.com/${GITHUB_REPO}/${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}"
}
# 安装系统依赖
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://github.com/${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://github.com/${GITHUB_REPO}.git" "$SOURCE_DIR" 2>&1; then
echo -e "${RED}克隆仓库失败,请检查网络连接和仓库地址${NC}"
echo -e "${YELLOW}仓库地址: https://github.com/${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
}
# 启动服务
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
install_dependencies
build_from_source
create_service
configure_firewall
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