#!/bin/bash # FlowStack 发布系统启动脚本 # 一键构建并启动前后端(前端产物嵌入 Go 单二进制,一个端口同时服务 API 与页面) # 用法: ./run.sh [start|stop|restart|build|dev|backend|status|help] set -e # ============================================================ # 配置区 # ============================================================ PROJECT_NAME="FlowStack" BACKEND_DIR="backend" FRONTEND_DIR="frontend" STANDALONE_BIN="flowstack" STANDALONE_PORT="${PORT:-9527}" FRONTEND_DEV_PORT=5173 STANDALONE_PID_FILE="$PWD/.flowstack.pid" STANDALONE_LOG_FILE="$PWD/logs/flowstack.log" BACKEND_DEV_PID_FILE="$PWD/.backend-dev.pid" # ============================================================ # 通用函数 # ============================================================ 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" 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 } wait_health() { local retries=0 while ! curl -s "http://localhost:$STANDALONE_PORT/health" > /dev/null 2>&1; do retries=$((retries + 1)) if [ $retries -ge 60 ]; then error "服务启动超时,请检查日志: $STANDALONE_LOG_FILE" return 1 fi sleep 1 done return 0 } # ============================================================ # 构建单二进制(前端 dist → backend/static → go build) # ============================================================ build_frontend() { info "构建前端资源..." cd "$SCRIPT_DIR/$FRONTEND_DIR" if [ ! -d "node_modules" ]; then warning "node_modules 不存在,正在安装依赖..." npm install # 允许 esbuild 安装其二进制(npm allow-scripts 策略) npm approve-scripts esbuild 2>/dev/null || true npm rebuild esbuild 2>/dev/null || true fi npm run build success "前端构建完成: $FRONTEND_DIR/dist" cd "$SCRIPT_DIR" } build_binary() { build_frontend info "拷贝前端产物到 backend/static 并编译 $STANDALONE_BIN..." rm -rf "$SCRIPT_DIR/$BACKEND_DIR/static" mkdir -p "$SCRIPT_DIR/$BACKEND_DIR/static" cp -R "$SCRIPT_DIR/$FRONTEND_DIR/dist/." "$SCRIPT_DIR/$BACKEND_DIR/static/" cd "$SCRIPT_DIR/$BACKEND_DIR" go build -trimpath -o "$SCRIPT_DIR/$STANDALONE_BIN" . cd "$SCRIPT_DIR" success "单二进制已生成: $SCRIPT_DIR/$STANDALONE_BIN" } # ============================================================ # 单二进制运行 # ============================================================ start_binary() { mkdir -p "$SCRIPT_DIR/logs" kill_port "$STANDALONE_PORT" info "启动 FlowStack (端口 $STANDALONE_PORT)..." PORT="$STANDALONE_PORT" GIN_MODE="${GIN_MODE:-release}" \ nohup "$SCRIPT_DIR/$STANDALONE_BIN" >> "$STANDALONE_LOG_FILE" 2>&1 & echo $! > "$STANDALONE_PID_FILE" if wait_health; then success "FlowStack 已启动: http://localhost:$STANDALONE_PORT (PID: $(cat "$STANDALONE_PID_FILE"))" else tail -20 "$STANDALONE_LOG_FILE" return 1 fi } stop_all() { info "正在停止服务..." if [ -f "$STANDALONE_PID_FILE" ]; then local pid=$(cat "$STANDALONE_PID_FILE" 2>/dev/null || echo "") if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null || true fi rm -f "$STANDALONE_PID_FILE" fi if [ -f "$BACKEND_DEV_PID_FILE" ]; then local pid=$(cat "$BACKEND_DEV_PID_FILE" 2>/dev/null || echo "") if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null || true fi rm -f "$BACKEND_DEV_PID_FILE" fi kill_port "$STANDALONE_PORT" kill_port "$FRONTEND_DEV_PORT" success "服务已停止" } show_status() { info "服务状态:" if [ -n "$(check_port $STANDALONE_PORT)" ]; then success "FlowStack: 运行中 (http://localhost:$STANDALONE_PORT)" else warning "FlowStack: 未运行" fi if [ -n "$(check_port $FRONTEND_DEV_PORT)" ]; then success "Vite dev: 运行中 (http://localhost:$FRONTEND_DEV_PORT)" fi [ -x "$SCRIPT_DIR/$STANDALONE_BIN" ] && success "二进制: $SCRIPT_DIR/$STANDALONE_BIN" } # ============================================================ # 开发模式:gin(9527) + vite(5173 代理 /api) # ============================================================ dev_mode() { info "开发模式: 后端 gin ($STANDALONE_PORT) + 前端 Vite ($FRONTEND_DEV_PORT)" kill_port "$STANDALONE_PORT" mkdir -p "$SCRIPT_DIR/logs" cd "$SCRIPT_DIR/$BACKEND_DIR" PORT="$STANDALONE_PORT" GIN_MODE=debug go run . >> "$SCRIPT_DIR/logs/backend-dev.log" 2>&1 & echo $! > "$BACKEND_DEV_PID_FILE" cd "$SCRIPT_DIR" if ! wait_health; then error "后端启动失败,查看 logs/backend-dev.log" return 1 fi success "后端已启动: http://localhost:$STANDALONE_PORT" cd "$SCRIPT_DIR/$FRONTEND_DIR" [ ! -d "node_modules" ] && { npm install; npm approve-scripts esbuild 2>/dev/null || true; npm rebuild esbuild 2>/dev/null || true; } info "启动 Vite(Ctrl+C 退出,退出后自动停止后端)..." trap 'kill $(cat "$BACKEND_DEV_PID_FILE" 2>/dev/null) 2>/dev/null; rm -f "$BACKEND_DEV_PID_FILE"; exit 0' INT TERM EXIT npm run dev } # ============================================================ # 命令入口 # ============================================================ show_help() { echo "$PROJECT_NAME 发布系统启动脚本" echo "" echo "用法: ./run.sh [命令]" echo "" echo "命令:" echo " start 构建并启动单二进制服务(默认,含前端资源)" echo " stop 停止所有服务" echo " restart 重启服务" echo " build 仅构建单二进制 $STANDALONE_BIN" echo " dev 开发模式:gin + Vite 热更新" echo " backend 仅启动后端 API(开发用)" echo " status 查看服务状态" echo " help 显示帮助" echo "" echo "环境变量:" echo " PORT 服务端口(默认 9527)" echo "" echo "示例:" echo " ./run.sh # 一键启动,访问 http://localhost:9527" echo " PORT=9000 ./run.sh # 指定端口" echo " ./run.sh dev # 前后端热更新开发" } main() { local command=${1:-start} # 依赖检查 if [[ "$command" =~ ^(start|build|dev|backend)$ ]]; then command -v go &> /dev/null || { error "Go 未安装"; exit 1; } fi if [[ "$command" =~ ^(start|build|dev)$ ]]; then command -v npm &> /dev/null || { error "npm 未安装,请先安装 Node.js"; exit 1; } fi case "$command" in start) info "========== $PROJECT_NAME ==========" [ ! -x "$SCRIPT_DIR/$STANDALONE_BIN" ] && { warning "未找到 $STANDALONE_BIN,开始构建(首次约 1-3 分钟)..." build_binary } start_binary && { echo "" success "========== FlowStack 已启动 ==========" info "访问地址: http://localhost:$STANDALONE_PORT" info "健康检查: http://localhost:$STANDALONE_PORT/health" info "停止服务: ./run.sh stop" echo "" } ;; stop) stop_all ;; restart) stop_all sleep 1 build_binary start_binary ;; build) build_binary ;; dev) dev_mode ;; backend) kill_port "$STANDALONE_PORT" mkdir -p "$SCRIPT_DIR/logs" cd "$SCRIPT_DIR/$BACKEND_DIR" PORT="$STANDALONE_PORT" GIN_MODE=debug go run . >> "$SCRIPT_DIR/logs/backend-dev.log" 2>&1 & echo $! > "$BACKEND_DEV_PID_FILE" cd "$SCRIPT_DIR" wait_health && success "后端 API: http://localhost:$STANDALONE_PORT (日志: logs/backend-dev.log)" ;; status) show_status ;; help|--help|-h) show_help ;; *) error "未知命令: $command" show_help exit 1 ;; esac } main "$@"