#!/bin/bash # PMFlow 启动器:后端 + 前端 + Cloudflare Quick Tunnel(自动输出临时公网地址) set -euo pipefail PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$PROJECT_DIR" GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' die() { echo -e "${RED}❌ $1${NC}" exit 1 } kill_port() { local port="$1" lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | xargs kill -9 2>/dev/null || true } wait_http_ok() { local url="$1" local max_retry="$2" local retry=1 while [ "$retry" -le "$max_retry" ]; do if curl -fsS "$url" >/dev/null 2>&1; then return 0 fi sleep 1 retry=$((retry + 1)) done return 1 } echo "🚀 PMFlow 启动器" echo "====================" [ -d "venv" ] || die "虚拟环境不存在,请先创建: python -m venv venv" [ -d "backend" ] || die "缺少 backend 目录" [ -d "frontend" ] || die "缺少 frontend 目录" command -v cloudflared >/dev/null 2>&1 || die "未安装 cloudflared" command -v npm >/dev/null 2>&1 || die "未安装 npm" mkdir -p logs rm -f logs/backend.pid logs/frontend.pid logs/tunnel.pid logs/public_url.txt rm -f logs/tunnel.log logs/tunnel-backend.log logs/tunnel-frontend.log echo "🧹 清理旧进程与端口..." pkill -f "cloudflared tunnel --url http://127.0.0.1:5174" >/dev/null 2>&1 || true pkill -f "cloudflared tunnel --url http://localhost:5174" >/dev/null 2>&1 || true kill_port 8000 kill_port 5173 kill_port 5174 sleep 1 echo -e "${GREEN}▶️ 启动后端 (8000)...${NC}" ( cd backend nohup "$PROJECT_DIR/venv/bin/uvicorn" app.main:app --host 0.0.0.0 --port 8000 --reload --reload-dir app > "$PROJECT_DIR/logs/backend.log" 2>&1 & echo $! > "$PROJECT_DIR/logs/backend.pid" ) BACKEND_PID="$(cat logs/backend.pid)" wait_http_ok "http://127.0.0.1:8000/healthz" 15 || die "后端启动失败,查看 logs/backend.log" echo -e "${GREEN}✅ 后端已启动 (PID: ${BACKEND_PID})${NC}" echo -e "${GREEN}▶️ 启动前端 (5174)...${NC}" ( cd frontend nohup npm run dev -- --host 0.0.0.0 --port 5174 > "$PROJECT_DIR/logs/frontend.log" 2>&1 & echo $! > "$PROJECT_DIR/logs/frontend.pid" ) FRONTEND_PID="$(cat logs/frontend.pid)" wait_http_ok "http://127.0.0.1:5174/login" 20 || wait_http_ok "http://127.0.0.1:5174" 10 || die "前端启动失败,查看 logs/frontend.log" echo -e "${GREEN}✅ 前端已启动 (PID: ${FRONTEND_PID})${NC}" echo -e "${GREEN}▶️ 启动 Cloudflare Quick Tunnel...${NC}" nohup cloudflared tunnel --url http://127.0.0.1:5174 > logs/tunnel.log 2>&1 & TUNNEL_PID=$! echo "$TUNNEL_PID" > logs/tunnel.pid TEMP_URL="" for _ in {1..25}; do TEMP_URL="$(grep -aEo 'https://[a-z0-9-]+\.trycloudflare\.com' logs/tunnel.log | head -n 1 || true)" [ -n "$TEMP_URL" ] && break sleep 1 done if [ -n "$TEMP_URL" ]; then echo "$TEMP_URL" > logs/public_url.txt echo -e "${GREEN}✅ Tunnel 已启动 (PID: ${TUNNEL_PID})${NC}" echo -e "🌐 公网地址(临时): ${YELLOW}${TEMP_URL}${NC}" else echo -e "${YELLOW}⚠️ Tunnel 已启动但暂未解析到地址,请查看 logs/tunnel.log${NC}" fi LAN_IP="$(ipconfig getifaddr en0 2>/dev/null || echo '本机IP')" echo echo -e "${GREEN}🎉 启动完成${NC}" echo "====================" echo "本地前端: http://127.0.0.1:5174" echo "本地后端: http://127.0.0.1:8000" echo "局域网前端: http://${LAN_IP}:5174" echo "API 文档: http://127.0.0.1:8000/docs" if [ -n "$TEMP_URL" ]; then echo "公网地址: ${TEMP_URL}" fi echo "日志: logs/backend.log | logs/frontend.log | logs/tunnel.log" echo "停止: ./stop.sh"