PMFlow/start.sh

260 lines
8.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_DIR"
MODE="${1:-dev}"
stop_legacy_conflicts() {
local pid cmd
if [[ "${PMFLOW_KEEP_LEGACY:-0}" == "1" ]]; then
echo "[PMFlow] 跳过旧链路冲突清理(PMFLOW_KEEP_LEGACY=1)"
return
fi
while read -r pid; do
[[ -z "${pid:-}" ]] && continue
cmd="$(ps -p "$pid" -o command= 2>/dev/null || true)"
if [[ "$cmd" == *"uvicorn app.main:app"* ]]; then
kill "$pid" 2>/dev/null || true
echo "[PMFlow] 已停止旧链路后端 pid=$pid"
fi
done < <(lsof -tiTCP:8000 -sTCP:LISTEN 2>/dev/null || true)
while read -r pid; do
[[ -z "${pid:-}" ]] && continue
cmd="$(ps -p "$pid" -o command= 2>/dev/null || true)"
if [[ "$cmd" == *"npm run dev -- --host 0.0.0.0 --port 5174"* ]] || [[ "$cmd" == *"vite --host 0.0.0.0 --port 5174"* ]]; then
kill "$pid" 2>/dev/null || true
echo "[PMFlow] 已停止旧链路前端 pid=$pid"
fi
done < <(lsof -tiTCP:5174 -sTCP:LISTEN 2>/dev/null || true)
}
extract_tunnel_url() {
local logfile="$1"
rg -o "https://[a-z0-9-]+\\.trycloudflare\\.com" "$logfile" 2>/dev/null \
| rg -v "^https://api\\.trycloudflare\\.com$" \
| tail -n 1
}
probe_tunnel_url() {
local url="$1"
local health_path="$2"
local code=""
code="$(curl -s -m 8 -o /dev/null -w "%{http_code}" "${url}${health_path}" || true)"
[[ "$code" =~ ^[0-9]{3}$ ]] || return 1
[[ "$code" -ge 200 && "$code" -lt 500 ]]
}
probe_tunnel_url_stable() {
local url="$1"
local health_path="$2"
local checks="${3:-2}"
local current=1
while [[ "$current" -le "$checks" ]]; do
probe_tunnel_url "$url" "$health_path" || return 1
if [[ "$current" -lt "$checks" ]]; then
sleep 1
fi
current=$((current + 1))
done
}
probe_local_origin() {
local port="$1"
local path="${2:-/}"
local code=""
code="$(curl -s -m 3 -o /dev/null -w "%{http_code}" "http://127.0.0.1:${port}${path}" || true)"
[[ "$code" =~ ^[0-9]{3}$ ]] || return 1
[[ "$code" -ge 200 && "$code" -lt 500 ]]
}
resolve_frontend_tunnel_port() {
local preferred="${PMFLOW_FRONTEND_TUNNEL_PORT:-${FRONTEND_PORT:-5174}}"
local candidates=("$preferred" 5174 5175 5176 5177 5178)
local seen=" "
local port=""
for port in "${candidates[@]}"; do
[[ "$seen" == *" ${port} "* ]] && continue
seen="${seen}${port} "
if probe_local_origin "$port" "/"; then
echo "$port"
return 0
fi
done
echo "$preferred"
}
start_or_reuse_tunnel() {
local name="$1"
local port="$2"
local health_path="${3:-/}"
local pidfile="logs/tunnel-${name}.pid"
local logfile="logs/tunnel-${name}.log"
local protocol="${PMFLOW_TUNNEL_PROTOCOL:-http2}"
# default: reuse existing tunnel URL; set PMFLOW_FORCE_NEW_TUNNEL=1 to force rotate
local force_new="${PMFLOW_FORCE_NEW_TUNNEL:-0}"
# default: probe existing URL before reuse; set 0 only if you explicitly want fast/stale reuse
local strict_check="${PMFLOW_STRICT_TUNNEL_CHECK:-1}"
local url=""
local max_attempts=2
local attempt=1
local new_pid=""
if [[ "$force_new" != "1" ]]; then
if [[ -f "$pidfile" ]]; then
local old_pid
old_pid="$(cat "$pidfile" 2>/dev/null || true)"
if [[ -n "${old_pid:-}" ]] && ps -p "$old_pid" >/dev/null 2>&1; then
url="$(extract_tunnel_url "$logfile")"
if [[ -n "$url" ]]; then
if [[ "$strict_check" != "1" ]] || probe_tunnel_url_stable "$url" "$health_path"; then
echo "$url"
return 0
fi
fi
fi
fi
fi
while [[ "$attempt" -le "$max_attempts" ]]; do
if [[ -f "$pidfile" ]]; then
local old_pid
old_pid="$(cat "$pidfile" 2>/dev/null || true)"
if [[ -n "${old_pid:-}" ]] && ps -p "$old_pid" >/dev/null 2>&1; then
kill "$old_pid" >/dev/null 2>&1 || true
fi
fi
pkill -f "cloudflared tunnel .*http://127.0.0.1:${port}" >/dev/null 2>&1 || true
rm -f "$pidfile" "$logfile"
nohup env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \
NO_PROXY="*" \
cloudflared tunnel --protocol "$protocol" --url "http://127.0.0.1:${port}" >"$logfile" 2>&1 &
new_pid="$!"
echo "$new_pid" >"$pidfile"
for _ in {1..35}; do
url="$(extract_tunnel_url "$logfile")"
if [[ -n "$url" ]]; then
if probe_tunnel_url_stable "$url" "$health_path"; then
echo "$url"
return 0
fi
fi
if [[ -n "$new_pid" ]] && ! ps -p "$new_pid" >/dev/null 2>&1; then
break
fi
sleep 1
done
attempt=$((attempt + 1))
done
echo ""
}
print_temp_public_urls() {
if [[ "${PMFLOW_NO_TUNNEL:-0}" == "1" ]]; then
echo "[PMFlow] 跳过临时公网隧道(PMFLOW_NO_TUNNEL=1)"
return
fi
if ! command -v cloudflared >/dev/null 2>&1; then
echo "[PMFlow] 未检测到 cloudflared,跳过临时公网地址生成"
return
fi
mkdir -p logs
local front_url back_url frontend_port
local expose_backend="${PMFLOW_EXPOSE_BACKEND_TUNNEL:-0}"
local protocol="${PMFLOW_TUNNEL_PROTOCOL:-http2}"
local frontend_watch="${PMFLOW_FRONTEND_TUNNEL_WATCH:-1}"
echo "[PMFlow] 隧道协议: ${protocol}"
frontend_port="$(resolve_frontend_tunnel_port)"
echo "[PMFlow] 隧道前端源站: http://127.0.0.1:${frontend_port}"
front_url=""
if [[ "$frontend_watch" == "1" ]]; then
bash scripts/tunnel_frontend_watch.sh start >/dev/null 2>&1 || true
for _ in {1..25}; do
front_url="$(rg '^frontend=' logs/public_url.txt | tail -n 1 | sed 's/^frontend=//' || true)"
if [[ -z "$front_url" ]]; then
front_url="$(sed -n '1p' logs/public_url.txt | rg '^https?://' -N || true)"
fi
if [[ -n "$front_url" ]]; then
break
fi
sleep 1
done
else
front_url="$(start_or_reuse_tunnel "frontend" "$frontend_port" "/")"
fi
back_url=""
if [[ "$expose_backend" == "1" ]]; then
back_url="$(start_or_reuse_tunnel "backend" "8000" "/healthz")"
fi
: > logs/public_url.txt
if [[ -n "$front_url" ]]; then
echo "frontend=$front_url" >> logs/public_url.txt
echo "[PMFlow] 临时前端地址: $front_url"
else
echo "[PMFlow] 临时前端地址: (等待中,请查看 logs/tunnel-frontend.log)"
fi
if [[ "$expose_backend" != "1" ]]; then
echo "[PMFlow] 临时后端地址: (已跳过,PMFLOW_EXPOSE_BACKEND_TUNNEL=0)"
else
if [[ -n "$back_url" ]]; then
echo "backend=$back_url" >> logs/public_url.txt
echo "[PMFlow] 临时后端地址: $back_url"
else
echo "[PMFlow] 临时后端地址: (等待中,请查看 logs/tunnel-backend.log)"
fi
fi
echo "[PMFlow] 公网地址已写入: logs/public_url.txt"
}
case "$MODE" in
dev|"")
echo "[PMFlow] 启动模式: 开发环境(Docker 热重载)"
stop_legacy_conflicts
bash scripts/docker_stack.sh dev-start
echo "[PMFlow] 前端地址: http://127.0.0.1:5174"
echo "[PMFlow] 后端地址: http://127.0.0.1:8000"
print_temp_public_urls
;;
prod)
echo "[PMFlow] 启动模式: 生产环境(Docker Compose)"
stop_legacy_conflicts
bash scripts/docker_stack.sh start
echo "[PMFlow] 前端地址: http://127.0.0.1:5174"
echo "[PMFlow] 后端地址: http://127.0.0.1:8000"
print_temp_public_urls
;;
urls)
if [[ -s logs/public_url.txt ]]; then
local_front_url="$(rg '^frontend=' logs/public_url.txt | tail -n 1 | sed 's/^frontend=//' || true)"
local_back_url="$(rg '^backend=' logs/public_url.txt | tail -n 1 | sed 's/^backend=//' || true)"
if [[ -z "$local_front_url" ]]; then
local_front_url="$(sed -n '1p' logs/public_url.txt | rg '^https?://' -N || true)"
fi
if [[ -z "$local_back_url" ]]; then
local_back_url="$(sed -n '2p' logs/public_url.txt | rg '^https?://' -N || true)"
fi
echo "[PMFlow] 当前公网地址:"
echo "前端: ${local_front_url:-未就绪}"
echo "后端: ${local_back_url:-未暴露}"
else
echo "[PMFlow] 未找到公网地址记录(logs/public_url.txt)"
exit 1
fi
;;
*)
echo "用法: $0 [dev|prod|urls]"
exit 1
;;
esac