112 lines
3.5 KiB
Bash
Executable File
112 lines
3.5 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
|
||
}
|
||
|
||
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"
|