#!/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 } wait_remote_ok() { local url="$1" local max_retry="$2" local retry=1 while [ "$retry" -le "$max_retry" ]; do if curl -kfsS --max-time 8 "$url" >/dev/null 2>&1; then return 0 fi sleep 2 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/tunnel-frontend.pid logs/tunnel-backend.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 pkill -f "cloudflared tunnel --url http://127.0.0.1:8000" >/dev/null 2>&1 || true pkill -f "cloudflared tunnel --url http://localhost:8000" >/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 Tunnels...${NC}" # Start frontend tunnel nohup cloudflared tunnel --url http://127.0.0.1:5174 > logs/tunnel-frontend.log 2>&1 & TUNNEL_FRONT_PID=$! echo "$TUNNEL_FRONT_PID" > logs/tunnel-frontend.pid # Start backend tunnel nohup cloudflared tunnel --url http://127.0.0.1:8000 > logs/tunnel-backend.log 2>&1 & TUNNEL_BACK_PID=$! echo "$TUNNEL_BACK_PID" > logs/tunnel-backend.pid TEMP_URL_FRONT="" TEMP_URL_BACK="" for _ in {1..25}; do if [ -z "$TEMP_URL_FRONT" ]; then TEMP_URL_FRONT="$(grep -aEo 'https://[a-z0-9-]+\.trycloudflare\.com' logs/tunnel-frontend.log | head -n 1 || true)" fi if [ -z "$TEMP_URL_BACK" ]; then TEMP_URL_BACK="$(grep -aEo 'https://[a-z0-9-]+\.trycloudflare\.com' logs/tunnel-backend.log | head -n 1 || true)" fi if [ -n "$TEMP_URL_FRONT" ] && [ -n "$TEMP_URL_BACK" ]; then break fi sleep 1 done if [ -n "$TEMP_URL_FRONT" ] && [ -n "$TEMP_URL_BACK" ]; then FRONT_OK=false BACK_OK=false if wait_remote_ok "${TEMP_URL_FRONT}/login" 25 || wait_remote_ok "${TEMP_URL_FRONT}" 25; then FRONT_OK=true fi if wait_remote_ok "${TEMP_URL_BACK}/healthz" 25; then BACK_OK=true fi echo "$TEMP_URL_FRONT" > logs/public_url.txt echo "$TEMP_URL_BACK" >> logs/public_url.txt if [ "$FRONT_OK" = true ] && [ "$BACK_OK" = true ]; then echo -e "${GREEN}✅ Tunnels 已启动且公网可访问${NC}" else echo -e "${YELLOW}⚠️ Tunnel 已启动,但公网健康检查未全部通过(请稍后再试)${NC}" fi echo -e "🌐 前端公网地址(临时): ${YELLOW}${TEMP_URL_FRONT}${NC}" echo -e "🌐 后端公网地址(临时): ${YELLOW}${TEMP_URL_BACK}${NC}" echo -e "${YELLOW}ℹ️ 前端公网默认通过 /api 代理访问本地后端,无需手动修改 VITE_API_BASE_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_FRONT" ]; then echo "前端公网: ${TEMP_URL_FRONT}" fi if [ -n "$TEMP_URL_BACK" ]; then echo "后端公网: ${TEMP_URL_BACK}" fi echo "日志: logs/backend.log | logs/frontend.log | logs/tunnel-*.log" echo "停止: ./stop.sh"