share.sh 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #!/usr/bin/env bash
  2. # ──────────────────────────────────────────────────────────────────────────
  3. # mode_procedure 查看服务 + Cloudflare 隧道 一键管理脚本
  4. #
  5. # ./share.sh start 启动 server.py(后台) + cloudflared 隧道(后台,http2),打印公网链接
  6. # ./share.sh stop 停掉两者
  7. # ./share.sh restart 重启两者(换新链接)
  8. # ./share.sh status 查看运行状态 + 当前链接
  9. # ./share.sh url 只打印当前公网链接
  10. #
  11. # 设计要点:
  12. # - 两个进程都用 nohup/setsid 脱离终端,关掉终端窗口也不会被 SIGHUP 杀掉。
  13. # - cloudflared 强制 --protocol http2,绕开公司网/VPN 常封的 UDP(QUIC)。
  14. # - 幂等:重复 start 会先清掉旧进程,不会起一堆。
  15. # ──────────────────────────────────────────────────────────────────────────
  16. set -euo pipefail
  17. # 项目目录 = 本脚本所在目录(shell/)的上一级
  18. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  19. PROJ_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
  20. PORT="${PORT:-8771}"
  21. SERVER_LOG="$PROJ_DIR/.server.log"
  22. CF_LOG="$PROJ_DIR/.cloudflared.log"
  23. SERVER_PIDF="$PROJ_DIR/.server.pid"
  24. CF_PIDF="$PROJ_DIR/.cloudflared.pid"
  25. PY="${PYTHON:-python}"
  26. # 选一个能后台脱离的方式:优先 setsid,macOS 没有就用 nohup
  27. _spawn() { # _spawn <logfile> <cmd...>
  28. local log="$1"; shift
  29. if command -v setsid >/dev/null 2>&1; then
  30. setsid "$@" >"$log" 2>&1 < /dev/null &
  31. else
  32. nohup "$@" >"$log" 2>&1 < /dev/null &
  33. fi
  34. echo $!
  35. }
  36. _alive() { [ -n "${1:-}" ] && kill -0 "$1" 2>/dev/null; }
  37. _server_running() { lsof -nP -iTCP:"$PORT" -sTCP:LISTEN >/dev/null 2>&1; }
  38. _extract_url() {
  39. # 优先看脚本自己的日志,兼容回退到手动启动时的 /tmp 日志;没找到不报错
  40. { grep -hEo "https://[a-z0-9-]+\.trycloudflare\.com" "$CF_LOG" /tmp/cf_tunnel.log 2>/dev/null || true; } | tail -1
  41. }
  42. start_server() {
  43. if _server_running; then
  44. echo "✅ server.py 已在跑 (:$PORT)"
  45. return
  46. fi
  47. echo "▶️ 启动 server.py …"
  48. ( cd "$PROJ_DIR" && _spawn "$SERVER_LOG" "$PY" server.py "$PORT" ) > "$SERVER_PIDF"
  49. # 等监听端口起来
  50. for _ in $(seq 1 20); do
  51. _server_running && break
  52. sleep 0.5
  53. done
  54. if _server_running; then
  55. echo "✅ server.py 已启动 (:$PORT) 日志: $SERVER_LOG"
  56. else
  57. echo "❌ server.py 启动失败,看日志: $SERVER_LOG"; tail -15 "$SERVER_LOG" || true; exit 1
  58. fi
  59. }
  60. start_tunnel() {
  61. echo "▶️ 启动 cloudflared 隧道 (http2) …"
  62. : > "$CF_LOG"
  63. _spawn "$CF_LOG" cloudflared tunnel --protocol http2 --url "http://localhost:$PORT" > "$CF_PIDF"
  64. # 等 URL 出现
  65. local url=""
  66. for _ in $(seq 1 40); do
  67. url="$(_extract_url)"
  68. [ -n "$url" ] && break
  69. sleep 0.5
  70. done
  71. if [ -n "$url" ]; then
  72. # 再等连接注册成功
  73. for _ in $(seq 1 20); do
  74. grep -q "Registered tunnel connection" "$CF_LOG" && break
  75. sleep 0.5
  76. done
  77. echo
  78. echo "🌐 公网链接(发给同事即可,无需局域网/改代理):"
  79. echo " $url"
  80. echo
  81. else
  82. echo "❌ 隧道未拿到地址,看日志: $CF_LOG"; tail -15 "$CF_LOG" || true; exit 1
  83. fi
  84. }
  85. stop_all() {
  86. echo "⏹ 停止隧道与服务 …"
  87. pkill -f "cloudflared tunnel --protocol http2 --url http://localhost:$PORT" 2>/dev/null && echo " - cloudflared 已停" || echo " - 无 cloudflared"
  88. # 杀掉占用端口的 server.py
  89. local pids; pids="$(lsof -nP -tiTCP:"$PORT" -sTCP:LISTEN 2>/dev/null || true)"
  90. if [ -n "$pids" ]; then
  91. echo "$pids" | xargs kill 2>/dev/null && echo " - server.py (:$PORT) 已停"
  92. else
  93. echo " - 无 server.py 监听 :$PORT"
  94. fi
  95. rm -f "$SERVER_PIDF" "$CF_PIDF"
  96. }
  97. status() {
  98. echo "── 状态 ──────────────────────────────"
  99. if _server_running; then echo "server.py : ✅ 运行中 (:$PORT)"; else echo "server.py : ❌ 未运行"; fi
  100. if pgrep -f "cloudflared tunnel .*localhost:$PORT" >/dev/null 2>&1; then
  101. echo "cloudflared: ✅ 运行中"
  102. else
  103. echo "cloudflared: ❌ 未运行"
  104. fi
  105. local url; url="$(_extract_url)"
  106. [ -n "$url" ] && echo "公网链接 : $url" || echo "公网链接 : (无)"
  107. # 本地健康检查(绕代理)
  108. local code; code="$(curl -s -m3 --noproxy '*' -o /dev/null -w '%{http_code}' "http://127.0.0.1:$PORT/" 2>/dev/null || echo 000)"
  109. echo "本地自检 : HTTP $code"
  110. echo "─────────────────────────────────────"
  111. }
  112. case "${1:-start}" in
  113. start) start_server; start_tunnel ;;
  114. stop) stop_all ;;
  115. restart) stop_all; sleep 1; start_server; start_tunnel ;;
  116. status) status ;;
  117. url) u="$(_extract_url)"; [ -n "$u" ] && echo "$u" || { echo "(隧道未运行)"; exit 1; } ;;
  118. *) echo "用法: $0 {start|stop|restart|status|url}"; exit 1 ;;
  119. esac