#!/bin/bash # lokistack 启动脚本 # 一键启动前后端服务,支持日志轮转 # 用法: ./run.sh [start|stop|restart|status|frontend|backend|help] set -e # ============================================================ # 配置区 # ============================================================ PROJECT_NAME="lokistack" BACKEND_PORT=8080 FRONTEND_PORT=3000 BACKEND_DIR="backend" FRONTEND_DIR="frontend" BACKEND_BIN="bin/lokistack" # 日志配置 MAX_LOG_FILES=3 MAX_LOG_SIZE=$((50 * 1024 * 1024)) # 50MB # ============================================================ # 通用函数 # ============================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' 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"; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # PID 文件 BACKEND_PID_FILE="$SCRIPT_DIR/$BACKEND_DIR/.backend.pid" FRONTEND_PID_FILE="$SCRIPT_DIR/$FRONTEND_DIR/.frontend.pid" # 检查端口是否被占用 check_port() { lsof -ti:$1 2>/dev/null | head -1 } # 释放端口 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 fi } # 日志轮转(仅启动时调用) 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") 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 [ -f "${log_file}.${i}" ] && mv "${log_file}.${i}" "${log_file}.$((i + 1))" done mv "$log_file" "${log_file}.1" info "日志已轮转: $log_file" fi } # 检测 Go detect_go() { if command -v go &> /dev/null; then echo "go" elif [ -f "$HOME/go/bin/go" ]; then echo "$HOME/go/bin/go" else echo "" fi } cleanup() { : } # ============================================================ # 后端服务 (Go + Gin) # ============================================================ start_backend() { info "正在启动后端服务..." cd "$SCRIPT_DIR/$BACKEND_DIR" mkdir -p logs rotate_log "logs/backend.log" kill_port $BACKEND_PORT local GO_CMD=$(detect_go) if [ -z "$GO_CMD" ]; then error "Go 未安装,请先安装 Go 1.23+" return 1 fi # 编译二进制(若不存在或源码更新) local NEED_BUILD=0 if [ ! -f "$BACKEND_BIN" ]; then NEED_BUILD=1 elif [ -n "$(find . -name '*.go' -newer "$BACKEND_BIN" -not -path './bin/*' 2>/dev/null | head -1)" ]; then NEED_BUILD=1 fi if [ "$NEED_BUILD" = "1" ]; then warning "编译后端二进制..." CGO_ENABLED=0 GOPROXY=https://goproxy.cn,direct $GO_CMD build -trimpath -ldflags="-s -w" -o "$BACKEND_BIN" . || { error "后端编译失败" return 1 } fi # 启动(后台) export SERVER_PORT=$BACKEND_PORT export DATA_DIR="$SCRIPT_DIR/data" export JWT_SECRET="${JWT_SECRET:-lokistack-secret-key-change-in-production}" # v2 安全模型:AES 加密需要独立密钥(用于主机密码/私钥/kubeconfig 等敏感字段)。 # config.go 在 ENCRYPTION_KEY 缺失或 <32 字符时会 log.Fatal 拒绝启动,故此处提供开发默认值。 if [ -z "${ENCRYPTION_KEY:-}" ]; then warning "未设置 ENCRYPTION_KEY,使用开发默认值。生产环境请通过 .env 或环境变量提供独立随机密钥!" export ENCRYPTION_KEY="lokistack-dev-encryption-key-change-me!!" fi nohup "./$BACKEND_BIN" >> logs/backend.log 2>&1 & echo $! > "$BACKEND_PID_FILE" # 等待服务启动 local retries=0 while [ -z "$(check_port $BACKEND_PORT)" ]; do retries=$((retries + 1)) if [ $retries -ge 30 ]; then error "后端服务启动超时,请检查日志: $BACKEND_DIR/logs/backend.log" return 1 fi sleep 1 done success "后端服务已启动 (PID: $(cat "$BACKEND_PID_FILE"), 端口: $BACKEND_PORT)" info "API: http://localhost:$BACKEND_PORT/api/v1" info "默认登录: admin / admin123" return 0 } # 等待进程退出:先 SIGTERM 优雅关停,超时再 SIGKILL graceful_kill() { local pid=$1 local name=$2 local grace=${3:-8} if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then return 0 fi info "正在优雅停止 $name (PID: $pid)..." kill -TERM "$pid" 2>/dev/null || true local i=0 while kill -0 "$pid" 2>/dev/null && [ $i -lt $((grace * 2)) ]; do sleep 0.5 i=$((i + 1)) done if kill -0 "$pid" 2>/dev/null; then warning "$name 未在 ${grace}s 内退出,强制终止..." kill -KILL "$pid" 2>/dev/null || true fi success "$name 已停止" } stop_backend() { if [ -f "$BACKEND_PID_FILE" ]; then local pid=$(cat "$BACKEND_PID_FILE" 2>/dev/null || echo "") graceful_kill "$pid" "后端服务" 8 rm -f "$BACKEND_PID_FILE" fi # 兜底:清理仍占用端口的残留进程(非 PID 文件管理的) kill_port $BACKEND_PORT } # ============================================================ # 前端服务 (Vite Dev Server) # ============================================================ start_frontend() { info "正在启动前端服务..." cd "$SCRIPT_DIR/$FRONTEND_DIR" mkdir -p logs rotate_log "logs/dev.log" kill_port $FRONTEND_PORT if [ ! -d "node_modules" ]; then warning "node_modules 不存在,正在安装依赖..." npm install || { error "依赖安装失败"; return 1; } fi nohup npm run dev >> logs/dev.log 2>&1 & echo $! > "$FRONTEND_PID_FILE" # 等待服务启动 local retries=0 while [ -z "$(check_port $FRONTEND_PORT)" ]; do retries=$((retries + 1)) if [ $retries -ge 30 ]; then error "前端服务启动超时,请检查日志: $FRONTEND_DIR/logs/dev.log" return 1 fi sleep 1 done success "前端服务已启动 (PID: $(cat "$FRONTEND_PID_FILE"), 端口: $FRONTEND_PORT)" info "前端地址: http://localhost:$FRONTEND_PORT" return 0 } stop_frontend() { if [ -f "$FRONTEND_PID_FILE" ]; then local pid=$(cat "$FRONTEND_PID_FILE" 2>/dev/null || echo "") graceful_kill "$pid" "前端服务" 5 rm -f "$FRONTEND_PID_FILE" fi kill_port $FRONTEND_PORT } # ============================================================ # 命令 # ============================================================ stop() { info "正在停止服务..." stop_backend stop_frontend info "所有服务已停止" } show_status() { info "服务状态:" if [ -n "$(check_port $BACKEND_PORT)" ]; then success "后端服务: 运行中 (PID: $(check_port $BACKEND_PORT), 端口: $BACKEND_PORT)" else warning "后端服务: 未运行" fi if [ -n "$(check_port $FRONTEND_PORT)" ]; then success "前端服务: 运行中 (PID: $(check_port $FRONTEND_PORT), 端口: $FRONTEND_PORT)" else warning "前端服务: 未运行" 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 " help 显示帮助信息" echo "" echo "端口:" echo " 后端 API: http://localhost:$BACKEND_PORT/api/v1" echo " 前端页面: http://localhost:$FRONTEND_PORT" echo "" echo "示例:" echo " ./run.sh # 启动所有服务" echo " ./run.sh stop # 停止服务" echo " ./run.sh backend # 仅启动后端" } # ============================================================ # 主入口 # ============================================================ trap cleanup EXIT INT TERM main() { local command=${1:-start} case "$command" in start) info "========== $PROJECT_NAME 启动脚本 ==========" if [ -z "$(detect_go)" ]; then error "Go 未安装,请先安装 Go 1.23+" 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 "后端 API: http://localhost:$BACKEND_PORT/api/v1" info "默认登录: admin / admin123" echo "" info "使用 './run.sh stop' 停止服务" info "使用 './run.sh status' 查看状态" else error "服务启动失败" exit 1 fi ;; stop) stop ;; restart) stop sleep 2 main start ;; backend) info "========== $PROJECT_NAME (仅后端) ==========" start_backend show_status ;; frontend) info "========== $PROJECT_NAME (仅前端) ==========" start_frontend show_status ;; status) show_status ;; help|--help|-h) show_help ;; *) error "未知命令: $command" show_help exit 1 ;; esac } main "$@"