164 lines
5.4 KiB
Bash
Executable File
164 lines
5.4 KiB
Bash
Executable File
#!/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"
|