feat: run.sh四模式(dev/docker/build/binary) + CLI flags + Redka内嵌Redis + Agent复制修复 + 服务器编辑IP字段 + 安装Agent按钮

This commit is contained in:
attacker committed 2026-06-18 03:36:04 +00:00
1 parent d0b94d39c0
commit 2354959811
14 files changed
+1355 -575

No files matched your search

+371 -121
View File
@@ -1,19 +1,27 @@
#!/bin/bash
# EZOps AIOps 智能运维平台 - 启动脚本
# 一键启动前后端服务,支持日志轮转
# 用法: ./run.sh [start|stop|restart|status|frontend|backend|help]
#===========================================================================
# AIOps 智能运维平台 - 统一启动脚本
# 支持三种运行模式:本地开发 / Docker容器部署 / 独立二进制
#
# 用法:
# ./run.sh dev 本地开发模式(前后端热重载)
# ./run.sh docker up Docker Compose 容器部署
# ./run.sh build 编译独立二进制 ./aiops
# ./run.sh binary 编译并启动独立二进制
# ./run.sh help 查看帮助
#===========================================================================
set -e
# ============================================================
# 配置区
# ============================================================
PROJECT_NAME="EZOps AIOps"
PROJECT_NAME="AIOps 智能运维平台"
BACKEND_PORT=8080
FRONTEND_PORT=5173
BACKEND_DIR="backend"
FRONTEND_DIR="frontend"
BACKEND_BIN="aiops-server"
STANDALONE_BIN="aiops"
# 日志配置
MAX_LOG_FILES=3
@@ -26,12 +34,14 @@ RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
success() { echo -e "${GREEN}[OK]${NC} $1"; }
warning() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERR]${NC} $1"; }
banner() { echo -e "${CYAN}$1${NC}"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
@@ -40,27 +50,49 @@ cd "$SCRIPT_DIR"
BACKEND_PID_FILE="$SCRIPT_DIR/$BACKEND_DIR/.backend.pid"
FRONTEND_PID_FILE="$SCRIPT_DIR/$FRONTEND_DIR/.frontend.pid"
# 检测操作系统
detect_os() {
case "$(uname -s)" in
Darwin) echo "macos" ;;
Linux) echo "linux" ;;
*) echo "unknown" ;;
esac
}
OS=$(detect_os)
# 检查端口是否被占用
check_port() {
lsof -ti:$1 2>/dev/null | head -1
if [ "$OS" = "macos" ]; then
lsof -ti:$1 2>/dev/null | head -1
else
ss -tlnp 2>/dev/null | grep -q ":$1 " && echo "in-use" || echo ""
fi
}
# 释放端口
kill_port() {
local pids=$(lsof -ti:$1 2>/dev/null || echo "")
if [ -n "$pids" ]; then
info "释放端口 $1..."
echo "$pids" | xargs kill -9 2>/dev/null || true
sleep 1
local port=$1
if [ "$OS" = "macos" ]; then
local pids=$(lsof -ti:$port 2>/dev/null || echo "")
[ -n "$pids" ] && echo "$pids" | xargs kill -9 2>/dev/null || true
else
fuser -k ${port}/tcp 2>/dev/null || true
fi
sleep 0.5
}
# 日志轮转(仅启动时调用)
# 日志轮转
rotate_log() {
local log_file=$1
[ ! -f "$log_file" ] && return 0
local file_size=$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file" 2>/dev/null || echo "0")
local file_size
if [ "$OS" = "macos" ]; then
file_size=$(stat -f%z "$log_file" 2>/dev/null || echo "0")
else
file_size=$(stat -c%s "$log_file" 2>/dev/null || echo "0")
fi
if [ "$file_size" -gt "$MAX_LOG_SIZE" ]; then
[ -f "${log_file}.${MAX_LOG_FILES}" ] && rm -f "${log_file}.${MAX_LOG_FILES}"
for i in $(seq $((MAX_LOG_FILES - 1)) -1 1); do
@@ -72,20 +104,18 @@ rotate_log() {
}
# ============================================================
# 后端服务
# 后端服务(本地开发模式)
# ============================================================
start_backend() {
info "正在启动后端服务..."
info "启动后端服务..."
cd "$SCRIPT_DIR/$BACKEND_DIR"
mkdir -p logs
rotate_log "logs/server.log"
# 释放被占用的端口
kill_port $BACKEND_PORT
export GIN_MODE=debug
# 编译并运行(SQLite 需要 CGO)
info "编译 Go 程序..."
CGO_ENABLED=1 go build -o "$BACKEND_BIN" ./cmd/ 2>&1 || {
error "编译失败"
@@ -100,14 +130,14 @@ start_backend() {
while ! curl -s "http://localhost:$BACKEND_PORT/health" > /dev/null 2>&1; do
retries=$((retries + 1))
if [ $retries -ge 30 ]; then
error "后端服务启动超时,请检查日志: $BACKEND_DIR/logs/server.log"
error "后端启动超时,查看日志: $BACKEND_DIR/logs/server.log"
tail -20 logs/server.log 2>/dev/null
return 1
fi
sleep 1
done
success "后端服务已启动 (PID: $(cat "$BACKEND_PID_FILE"), 端口: $BACKEND_PORT)"
success "后端已启动 (PID: $(cat "$BACKEND_PID_FILE"), 端口: $BACKEND_PORT)"
return 0
}
@@ -116,7 +146,7 @@ stop_backend() {
local pid=$(cat "$BACKEND_PID_FILE" 2>/dev/null || echo "")
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
success "后端服务已停止"
success "后端已停止"
fi
rm -f "$BACKEND_PID_FILE"
fi
@@ -124,10 +154,10 @@ stop_backend() {
}
# ============================================================
# 前端服务
# 前端服务(本地开发模式)
# ============================================================
start_frontend() {
info "正在启动前端服务..."
info "启动前端服务..."
cd "$SCRIPT_DIR/$FRONTEND_DIR"
mkdir -p logs
rotate_log "logs/dev.log"
@@ -135,26 +165,25 @@ start_frontend() {
kill_port $FRONTEND_PORT
if [ ! -d "node_modules" ]; then
warning "node_modules 不存在,正在安装依赖..."
warning "安装前端依赖..."
npm install
fi
nohup npm run dev >> logs/dev.log 2>&1 &
echo $! > "$FRONTEND_PID_FILE"
# 等待服务启动
local retries=0
while ! curl -s "http://localhost:$FRONTEND_PORT" > /dev/null 2>&1; do
retries=$((retries + 1))
if [ $retries -ge 30 ]; then
error "前端服务启动超时,请检查日志: $FRONTEND_DIR/logs/dev.log"
error "前端启动超时,查看日志: $FRONTEND_DIR/logs/dev.log"
tail -20 logs/dev.log 2>/dev/null
return 1
fi
sleep 1
done
success "前端服务已启动 (PID: $(cat "$FRONTEND_PID_FILE"), 端口: $FRONTEND_PORT)"
success "前端已启动 (PID: $(cat "$FRONTEND_PID_FILE"), 端口: $FRONTEND_PORT)"
return 0
}
@@ -163,7 +192,7 @@ stop_frontend() {
local pid=$(cat "$FRONTEND_PID_FILE" 2>/dev/null || echo "")
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
success "前端服务已停止"
success "前端已停止"
fi
rm -f "$FRONTEND_PID_FILE"
fi
@@ -171,142 +200,363 @@ stop_frontend() {
}
# ============================================================
# 命令
# 本地开发命令
# ============================================================
stop() {
info "正在停止服务..."
stop_backend
stop_frontend
info "所有服务已停止"
cmd_dev() {
banner "========== $PROJECT_NAME (本地开发) =========="
# 检查依赖
if ! command -v go &> /dev/null; then
error "Go 未安装,请先安装 Go 1.21+"
exit 1
fi
if ! command -v npm &> /dev/null; then
error "npm 未安装,请先安装 Node.js 20+"
exit 1
fi
if start_backend && start_frontend; then
echo ""
banner "========== 所有服务已启动 =========="
info "前端地址: http://localhost:$FRONTEND_PORT"
info "后端地址: http://localhost:$BACKEND_PORT"
info "健康检查: http://localhost:$BACKEND_PORT/health"
info "数据库: SQLite (backend/sqlite3.db)"
echo ""
info "使用 './run.sh stop' 停止服务"
else
error "服务启动失败"
exit 1
fi
}
show_status() {
info "服务状态:"
cmd_stop() {
info "停止本地服务..."
stop_backend
stop_frontend
success "所有本地服务已停止"
}
cmd_restart() {
cmd_stop
sleep 1
cmd_dev
}
cmd_status() {
info "本地服务状态:"
if [ -n "$(check_port $BACKEND_PORT)" ]; then
success "后端服务: 运行中 (PID: $(check_port $BACKEND_PORT), 端口: $BACKEND_PORT)"
success "后端: 运行中 (端口 $BACKEND_PORT)"
else
warning "后端服务: 未运行"
warning "后端: 未运行"
fi
if [ -n "$(check_port $FRONTEND_PORT)" ]; then
success "前端服务: 运行中 (PID: $(check_port $FRONTEND_PORT), 端口: $FRONTEND_PORT)"
success "前端: 运行中 (端口 $FRONTEND_PORT)"
else
warning "前端服务: 未运行"
warning "前端: 未运行"
fi
# 显示数据库文件
if [ -f "$SCRIPT_DIR/$BACKEND_DIR/sqlite3.db" ]; then
local db_size=$(du -h "$SCRIPT_DIR/$BACKEND_DIR/sqlite3.db" | cut -f1)
info "数据库: SQLite (sqlite3.db, $db_size)"
fi
}
show_help() {
echo "$PROJECT_NAME 启动脚本"
echo ""
echo "用法: ./run.sh [命令]"
echo ""
echo "命令:"
echo " start 启动前后端服务 (默认)"
echo " stop 停止前后端服务"
echo " restart 重启前后端服务"
echo " backend 仅启动后端服务"
echo " frontend 仅启动前端服务"
echo " status 查看服务状态"
echo " logs 查看实时日志"
echo " help 显示帮助信息"
echo ""
echo "示例:"
echo " ./run.sh # 启动所有服务"
echo " ./run.sh stop # 停止服务"
echo " ./run.sh restart # 重启服务"
echo " ./run.sh backend # 仅启动后端"
echo " ./run.sh logs # 查看实时日志"
}
show_logs() {
cmd_logs() {
info "实时日志 (Ctrl+C 退出):"
echo ""
# 后端日志
local pids=()
if [ -f "$SCRIPT_DIR/$BACKEND_DIR/logs/server.log" ]; then
echo "=== 后端日志 ==="
tail -f "$SCRIPT_DIR/$BACKEND_DIR/logs/server.log" &
local bg_pid=$!
pids+=($!)
fi
# 前端日志
if [ -f "$SCRIPT_DIR/$FRONTEND_DIR/logs/dev.log" ]; then
echo "=== 前端日志 ==="
tail -f "$SCRIPT_DIR/$FRONTEND_DIR/logs/dev.log" &
local bg_pid2=$!
pids+=($!)
fi
trap "kill $bg_pid $bg_pid2 2>/dev/null; exit 0" INT TERM
trap "kill ${pids[*]} 2>/dev/null; exit 0" INT TERM
wait
}
# ============================================================
# Docker Compose 部署
# ============================================================
cmd_docker() {
local action=${1:-help}
if ! command -v docker &> /dev/null; then
error "Docker 未安装,请先安装 Docker"
exit 1
fi
case "$action" in
up)
banner "========== Docker Compose 启动 =========="
docker compose up -d --build
echo ""
info "等待服务就绪..."
sleep 5
docker compose ps
echo ""
info "前端地址: http://localhost:80"
info "后端地址: http://localhost:8080"
info "健康检查: http://localhost:8080/health"
;;
down)
info "停止 Docker Compose..."
docker compose down
success "容器已停止"
;;
build)
info "构建 Docker 镜像..."
docker compose build
success "镜像构建完成"
;;
restart)
info "重启 Docker Compose..."
docker compose restart
success "容器已重启"
;;
logs)
info "Docker 实时日志 (Ctrl+C 退出):"
docker compose logs -f
;;
ps|status)
docker compose ps
;;
help|--help|-h)
echo "Docker Compose 部署命令:"
echo " ./run.sh docker up 构建并启动所有容器"
echo " ./run.sh docker down 停止并移除容器"
echo " ./run.sh docker build 仅构建镜像"
echo " ./run.sh docker restart 重启容器"
echo " ./run.sh docker logs 查看实时日志"
echo " ./run.sh docker ps 查看容器状态"
;;
*)
error "未知 docker 子命令: $action"
cmd_docker help
exit 1
;;
esac
}
# ============================================================
# 编译独立二进制
# ============================================================
cmd_build() {
local target=${1:-binary}
case "$target" in
binary|aiops)
banner "========== 编译独立二进制 =========="
cd "$SCRIPT_DIR/$BACKEND_DIR"
# 确保 config.yaml 存在(二进制模式默认 SQLite)
if [ ! -f "config.yaml" ]; then
info "生成默认 config.yaml (SQLite + 无Redis)..."
cat > config.yaml << 'YAML'
mode: "release"
port: "8080"
log_level: "info"
database:
driver: "sqlite"
host: "localhost"
port: 3306
user: ""
password: ""
dbname: "data/aiops.db"
charset: "utf8mb4"
max_conn: 100
max_idle: 10
redis:
host: "localhost"
port: 6379
password: ""
db: 0
jwt:
secret: "aiops-jwt-secret-change-in-production"
expire_hour: 24
websocket:
read_buffer_size: 1024
write_buffer_size: 1024
YAML
fi
# 静态编译(CGO_ENABLED=1 因为 SQLite 需要 CGO)
info "编译中..."
CGO_ENABLED=1 GOPROXY=https://goproxy.cn,direct GONOSUMCHECK='*' GOTOOLCHAIN=go1.22.2 go build -ldflags="-w -s" -o "$STANDALONE_BIN" ./cmd/
if [ -f "$STANDALONE_BIN" ]; then
local bin_size=$(du -h "$STANDALONE_BIN" | cut -f1)
success "编译完成: $BACKEND_DIR/$STANDALONE_BIN ($bin_size)"
echo ""
info "使用方法:"
echo " ./$BACKEND_DIR/$STANDALONE_BIN # 默认启动 (SQLite, 端口 8080)"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --port 9090 # 自定义端口"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --db-driver mysql \\"
echo " --db-host localhost --db-user root \\"
echo " --db-password pass --db-name aiops # MySQL 模式"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --config config.yaml # 使用配置文件"
else
error "编译失败"
exit 1
fi
;;
agent)
banner "========== 编译 Agent =========="
cd "$SCRIPT_DIR/agent"
info "编译 Linux AMD64..."
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o aiops-agent-linux-amd64 .
success "aiops-agent-linux-amd64 ($(du -h aiops-agent-linux-amd64 | cut -f1))"
info "编译 Linux ARM64..."
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-w -s" -o aiops-agent-linux-arm64 .
success "aiops-agent-linux-arm64 ($(du -h aiops-agent-linux-arm64 | cut -f1))"
if [ "$OS" = "macos" ]; then
info "编译 macOS (当前平台)..."
CGO_ENABLED=0 go build -ldflags="-w -s" -o aiops-agent .
success "aiops-agent ($(du -h aiops-agent | cut -f1))"
fi
;;
frontend)
info "构建前端生产包..."
cd "$SCRIPT_DIR/$FRONTEND_DIR"
npm run build
success "前端构建完成: $FRONTEND_DIR/dist/"
;;
help|--help|-h)
echo "编译命令:"
echo " ./run.sh build 编译独立二进制 aiops (默认 SQLite)"
echo " ./run.sh build agent 交叉编译 Agent (amd64+arm64)"
echo " ./run.sh build frontend 构建前端生产包"
;;
*)
error "未知构建目标: $target"
cmd_build help
exit 1
;;
esac
}
# ============================================================
# 独立二进制模式
# ============================================================
cmd_binary() {
cd "$SCRIPT_DIR/$BACKEND_DIR"
# 先编译(如果不存在)
if [ ! -f "$STANDALONE_BIN" ]; then
info "二进制不存在,先编译..."
cmd_build binary
fi
banner "========== 启动独立二进制 =========="
mkdir -p data logs uploads
# 传递额外参数
./"$STANDALONE_BIN" "$@"
}
# ============================================================
# 帮助
# ============================================================
cmd_help() {
echo ""
banner " $PROJECT_NAME 启动脚本"
echo ""
echo -e " ${GREEN}本地开发${NC}"
echo " ./run.sh dev 启动前后端开发服务 (SQLite, 端口 8080+5173)"
echo " ./run.sh stop 停止本地服务"
echo " ./run.sh restart 重启本地服务"
echo " ./run.sh status 查看本地服务状态"
echo " ./run.sh logs 查看实时日志"
echo ""
echo -e " ${CYAN}Docker 容器部署${NC}"
echo " ./run.sh docker up 构建并启动所有容器 (MySQL+Redis+Nginx)"
echo " ./run.sh docker down 停止并移除容器"
echo " ./run.sh docker build 仅构建镜像"
echo " ./run.sh docker restart 重启容器"
echo " ./run.sh docker logs 查看容器日志"
echo " ./run.sh docker ps 查看容器状态"
echo ""
echo -e " ${YELLOW}编译 & 独立二进制${NC}"
echo " ./run.sh build 编译独立二进制 ./$BACKEND_DIR/$STANDALONE_BIN"
echo " ./run.sh binary 编译+启动独立二进制"
echo " ./run.sh build agent 交叉编译 Agent (amd64+arm64)"
echo " ./run.sh build frontend 构建前端生产包"
echo ""
echo -e " ${BLUE}独立二进制用法${NC}"
echo " ./$BACKEND_DIR/$STANDALONE_BIN 默认启动 (SQLite, 端口 8080)"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --port 9090 自定义端口"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --config config.yaml 使用配置文件"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --db-driver mysql \\"
echo " --db-host 127.0.0.1 --db-user root \\"
echo " --db-password pass --db-name aiops MySQL 模式"
echo ""
echo -e " ${GREEN}快捷操作${NC}"
echo " ./run.sh 等同于 ./run.sh dev"
echo " ./run.sh help 显示此帮助"
echo ""
}
# ============================================================
# 主入口
# ============================================================
main() {
local command=${1:-start}
local cmd=${1:-dev}
case "$command" in
start)
info "========== $PROJECT_NAME 启动脚本 =========="
# 检查依赖
if ! command -v go &> /dev/null; then
error "Go 未安装,请先安装 Go"
exit 1
fi
if ! command -v npm &> /dev/null; then
error "npm 未安装,请先安装 Node.js"
exit 1
fi
if start_backend && start_frontend; then
echo ""
success "========== 所有服务已启动 =========="
info "前端地址: http://localhost:$FRONTEND_PORT"
info "后端地址: http://localhost:$BACKEND_PORT"
info "健康检查: http://localhost:$BACKEND_PORT/health"
info "数据库: SQLite (backend/sqlite3.db)"
echo ""
info "使用 './run.sh stop' 停止服务"
else
error "服务启动失败"
exit 1
fi
case "$cmd" in
# 本地开发
dev|start)
cmd_dev
;;
stop)
stop
cmd_stop
;;
restart)
stop
sleep 2
main start
;;
backend)
info "========== $PROJECT_NAME (仅后端) =========="
start_backend
show_status
;;
frontend)
info "========== $PROJECT_NAME (仅前端) =========="
start_frontend
show_status
cmd_restart
;;
status)
show_status
cmd_status
;;
logs)
show_logs
cmd_logs
;;
# Docker Compose
docker|compose)
shift
cmd_docker "$@"
;;
# 编译
build)
shift
cmd_build "$@"
;;
# 独立二进制
binary|bin)
shift
cmd_binary "$@"
;;
# 帮助
help|--help|-h)
show_help
cmd_help
;;
*)
error "未知命令: $command"
show_help
error "未知命令: $cmd"
cmd_help
exit 1
;;
esac