341 lines
9.0 KiB
Bash
Executable File
341 lines
9.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$PROJECT_DIR"
|
|
|
|
mkdir -p logs
|
|
|
|
PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH:-}"
|
|
URL_PATTERN='https://[a-z0-9-]+\.trycloudflare\.com'
|
|
WATCH_LOG="logs/tunnel-frontend-watch.log"
|
|
WATCH_STDOUT_LOG="logs/tunnel-frontend-watch.stdout"
|
|
WATCH_PID_FILE="logs/tunnel-frontend-watch.pid"
|
|
RUN_LOG="logs/tunnel-frontend.log"
|
|
TUNNEL_PID_FILE="logs/tunnel-frontend.pid"
|
|
PUBLIC_URL_FILE="logs/public_url.txt"
|
|
ACTIVE_CHILD_PID=""
|
|
|
|
is_pid_running() {
|
|
local pid="${1:-}"
|
|
[[ -n "$pid" ]] || return 1
|
|
ps -p "$pid" >/dev/null 2>&1
|
|
}
|
|
|
|
log_watch() {
|
|
local message="$1"
|
|
local line="[$(date '+%Y-%m-%d %H:%M:%S')] [watch] ${message}"
|
|
printf '%s\n' "$line" | tee -a "$WATCH_LOG"
|
|
}
|
|
|
|
log_watch_err() {
|
|
local message="$1"
|
|
local line="[$(date '+%Y-%m-%d %H:%M:%S')] [watch] ${message}"
|
|
printf '%s\n' "$line" | tee -a "$WATCH_LOG" >&2
|
|
}
|
|
|
|
read_frontend_url() {
|
|
local line=""
|
|
line="$(rg '^frontend=' "$PUBLIC_URL_FILE" 2>/dev/null | tail -n 1 || true)"
|
|
if [[ -n "$line" ]]; then
|
|
echo "${line#frontend=}"
|
|
return 0
|
|
fi
|
|
sed -n '1p' "$PUBLIC_URL_FILE" 2>/dev/null | rg '^https?://' -N || true
|
|
}
|
|
|
|
read_backend_url() {
|
|
local line=""
|
|
line="$(rg '^backend=' "$PUBLIC_URL_FILE" 2>/dev/null | tail -n 1 || true)"
|
|
if [[ -n "$line" ]]; then
|
|
echo "${line#backend=}"
|
|
return 0
|
|
fi
|
|
sed -n '2p' "$PUBLIC_URL_FILE" 2>/dev/null | rg '^https?://' -N || true
|
|
}
|
|
|
|
write_frontend_url() {
|
|
local frontend_url="$1"
|
|
local backend_url=""
|
|
backend_url="$(read_backend_url)"
|
|
: > "$PUBLIC_URL_FILE"
|
|
echo "frontend=$frontend_url" >> "$PUBLIC_URL_FILE"
|
|
if [[ -n "$backend_url" ]]; then
|
|
echo "backend=$backend_url" >> "$PUBLIC_URL_FILE"
|
|
fi
|
|
}
|
|
|
|
find_cloudflared_bin() {
|
|
local bin
|
|
bin="$(command -v cloudflared || true)"
|
|
if [[ -n "$bin" ]]; then
|
|
echo "$bin"
|
|
return 0
|
|
fi
|
|
for candidate in /opt/homebrew/bin/cloudflared /usr/local/bin/cloudflared; do
|
|
if [[ -x "$candidate" ]]; then
|
|
echo "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
echo ""
|
|
}
|
|
|
|
extract_tunnel_url() {
|
|
local logfile="$1"
|
|
rg -o "$URL_PATTERN" "$logfile" 2>/dev/null \
|
|
| rg -v "^https://api\\.trycloudflare\\.com$" \
|
|
| tail -n 1
|
|
}
|
|
|
|
probe_local_origin() {
|
|
local port="$1"
|
|
local code=""
|
|
code="$(curl --noproxy '*' -s -m 3 -o /dev/null -w "%{http_code}" "http://127.0.0.1:${port}/" || true)"
|
|
[[ "$code" =~ ^[0-9]{3}$ ]] || return 1
|
|
[[ "$code" -ge 200 && "$code" -lt 500 ]]
|
|
}
|
|
|
|
probe_tunnel_url() {
|
|
local url="$1"
|
|
local code=""
|
|
code="$(curl --noproxy '*' -s -m 8 -o /dev/null -w "%{http_code}" "${url}/" || true)"
|
|
[[ "$code" =~ ^[0-9]{3}$ ]] || return 1
|
|
[[ "$code" -ge 200 && "$code" -lt 500 ]]
|
|
}
|
|
|
|
probe_tunnel_url_stable() {
|
|
local url="$1"
|
|
local checks="${2:-2}"
|
|
local current=1
|
|
while [[ "$current" -le "$checks" ]]; do
|
|
probe_tunnel_url "$url" || return 1
|
|
if [[ "$current" -lt "$checks" ]]; then
|
|
sleep 1
|
|
fi
|
|
current=$((current + 1))
|
|
done
|
|
}
|
|
|
|
resolve_frontend_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"
|
|
}
|
|
|
|
is_watch_running() {
|
|
if [[ ! -f "$WATCH_PID_FILE" ]]; then
|
|
return 1
|
|
fi
|
|
local pid
|
|
pid="$(cat "$WATCH_PID_FILE" 2>/dev/null || true)"
|
|
if [[ -z "$pid" ]]; then
|
|
return 1
|
|
fi
|
|
if ps -p "$pid" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
cleanup_children() {
|
|
if [[ -n "${ACTIVE_CHILD_PID:-}" ]] && is_pid_running "$ACTIVE_CHILD_PID"; then
|
|
kill "$ACTIVE_CHILD_PID" >/dev/null 2>&1 || true
|
|
fi
|
|
if [[ -f "$TUNNEL_PID_FILE" ]]; then
|
|
local pid
|
|
pid="$(cat "$TUNNEL_PID_FILE" 2>/dev/null || true)"
|
|
if [[ -n "$pid" ]] && is_pid_running "$pid"; then
|
|
kill "$pid" >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
}
|
|
|
|
stop_watch() {
|
|
local stopped=0
|
|
if [[ -f "$WATCH_PID_FILE" ]]; then
|
|
local wpid
|
|
wpid="$(cat "$WATCH_PID_FILE" 2>/dev/null || true)"
|
|
if [[ -n "$wpid" ]] && is_pid_running "$wpid"; then
|
|
kill "$wpid" >/dev/null 2>&1 || true
|
|
stopped=1
|
|
fi
|
|
fi
|
|
cleanup_children
|
|
rm -f "$WATCH_PID_FILE" "$TUNNEL_PID_FILE"
|
|
if [[ "$stopped" -eq 1 ]]; then
|
|
echo "[前端隧道守护] 已停止"
|
|
else
|
|
echo "[前端隧道守护] 未运行"
|
|
fi
|
|
}
|
|
|
|
print_status() {
|
|
local tunnel_pid=""
|
|
if [[ -f "$TUNNEL_PID_FILE" ]]; then
|
|
tunnel_pid="$(cat "$TUNNEL_PID_FILE" 2>/dev/null || true)"
|
|
fi
|
|
if is_watch_running; then
|
|
local wpid cpid
|
|
wpid="$(cat "$WATCH_PID_FILE" 2>/dev/null || true)"
|
|
cpid="${tunnel_pid:-$(cat "$TUNNEL_PID_FILE" 2>/dev/null || true)}"
|
|
echo "[前端隧道守护] 运行中 watch_pid=${wpid:-unknown} tunnel_pid=${cpid:-unknown}"
|
|
else
|
|
echo "[前端隧道守护] 未运行"
|
|
fi
|
|
local frontend_url backend_url
|
|
frontend_url="$(read_frontend_url)"
|
|
backend_url="$(read_backend_url)"
|
|
if [[ -n "$frontend_url" ]]; then
|
|
echo "前端地址: $frontend_url"
|
|
if is_pid_running "$tunnel_pid"; then
|
|
echo "前端健康: tunnel进程运行中"
|
|
else
|
|
echo "前端健康: tunnel进程未运行"
|
|
fi
|
|
else
|
|
echo "前端地址: (空)"
|
|
fi
|
|
if [[ -n "$backend_url" ]]; then
|
|
echo "后端地址: $backend_url"
|
|
else
|
|
echo "后端地址: (未配置/未暴露)"
|
|
fi
|
|
}
|
|
|
|
start_watch_background() {
|
|
if is_watch_running; then
|
|
print_status
|
|
return 0
|
|
fi
|
|
nohup bash "$0" run >"$WATCH_STDOUT_LOG" 2>&1 &
|
|
local pid="$!"
|
|
echo "$pid" >"$WATCH_PID_FILE"
|
|
echo "[前端隧道守护] 已启动 pid=$pid"
|
|
sleep 1
|
|
tail -n 5 "$WATCH_STDOUT_LOG" 2>/dev/null || true
|
|
}
|
|
|
|
run_watch_loop() {
|
|
local cf_bin
|
|
cf_bin="$(find_cloudflared_bin)"
|
|
if [[ -z "$cf_bin" ]]; then
|
|
log_watch_err "cloudflared_not_found"
|
|
exit 1
|
|
fi
|
|
|
|
echo "$$" >"$WATCH_PID_FILE"
|
|
log_watch "start bin=$cf_bin"
|
|
trap 'cleanup_children; rm -f "$WATCH_PID_FILE"; log_watch "exit code=$?"' EXIT INT TERM
|
|
|
|
while true; do
|
|
local frontend_port tunnel_protocol child_pid published current_url url
|
|
local strict_public_health public_health_interval public_health_failure_threshold
|
|
local unhealthy_count last_health_check_at
|
|
frontend_port="$(resolve_frontend_port)"
|
|
tunnel_protocol="${PMFLOW_TUNNEL_PROTOCOL_FRONTEND:-${PMFLOW_TUNNEL_PROTOCOL:-http2}}"
|
|
strict_public_health="${PMFLOW_TUNNEL_STRICT_PUBLIC_HEALTH:-0}"
|
|
public_health_interval="${PMFLOW_TUNNEL_PUBLIC_HEALTH_INTERVAL_SECONDS:-30}"
|
|
public_health_failure_threshold="${PMFLOW_TUNNEL_PUBLIC_HEALTH_FAILURE_THRESHOLD:-5}"
|
|
: > "$RUN_LOG"
|
|
log_watch "origin=http://127.0.0.1:${frontend_port} protocol=${tunnel_protocol}"
|
|
|
|
env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \
|
|
NO_PROXY="*" \
|
|
"$cf_bin" tunnel --protocol "$tunnel_protocol" --url "http://127.0.0.1:${frontend_port}" >"$RUN_LOG" 2>&1 &
|
|
child_pid="$!"
|
|
ACTIVE_CHILD_PID="$child_pid"
|
|
echo "$child_pid" > "$TUNNEL_PID_FILE"
|
|
|
|
published="0"
|
|
current_url=""
|
|
for _ in {1..90}; do
|
|
url="$(extract_tunnel_url "$RUN_LOG" || true)"
|
|
if [[ -n "$url" ]]; then
|
|
if [[ "$strict_public_health" == "1" ]] && ! probe_tunnel_url_stable "$url"; then
|
|
:
|
|
else
|
|
if [[ "$url" != "$current_url" ]]; then
|
|
write_frontend_url "$url"
|
|
log_watch "url=$url"
|
|
fi
|
|
current_url="$url"
|
|
published="1"
|
|
break
|
|
fi
|
|
fi
|
|
if ! is_pid_running "$child_pid"; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
unhealthy_count=0
|
|
last_health_check_at=0
|
|
while is_pid_running "$child_pid"; do
|
|
if [[ "$strict_public_health" == "1" && "$published" == "1" && -n "$current_url" ]]; then
|
|
if (( $(date +%s) - last_health_check_at >= public_health_interval )); then
|
|
last_health_check_at="$(date +%s)"
|
|
if probe_tunnel_url "$current_url"; then
|
|
unhealthy_count=0
|
|
else
|
|
unhealthy_count=$((unhealthy_count + 1))
|
|
log_watch_err "health_check_failed url=${current_url} count=${unhealthy_count}"
|
|
fi
|
|
if [[ "$unhealthy_count" -ge "$public_health_failure_threshold" ]]; then
|
|
log_watch_err "url_unhealthy_restart url=${current_url}"
|
|
kill "$child_pid" >/dev/null 2>&1 || true
|
|
break
|
|
fi
|
|
fi
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
wait "$child_pid" || true
|
|
ACTIVE_CHILD_PID=""
|
|
if [[ "$published" != "1" ]]; then
|
|
log_watch_err "exited_before_url"
|
|
else
|
|
log_watch_err "exited_restart"
|
|
fi
|
|
sleep 2
|
|
done
|
|
}
|
|
|
|
cmd="${1:-run}"
|
|
case "$cmd" in
|
|
run)
|
|
run_watch_loop
|
|
;;
|
|
start)
|
|
start_watch_background
|
|
;;
|
|
stop)
|
|
stop_watch
|
|
;;
|
|
status)
|
|
print_status
|
|
;;
|
|
url)
|
|
front="$(read_frontend_url)"
|
|
back="$(read_backend_url)"
|
|
echo "前端地址: ${front:-未就绪}"
|
|
echo "后端地址: ${back:-未配置/未暴露}"
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {run|start|stop|status|url}"
|
|
exit 1
|
|
;;
|
|
esac
|