137 lines
5.7 KiB
Bash
Executable File
137 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 用 ngrok 暴露本机 8000 端口,并把公网 URL 写入 .env 的 CALLBACK_BASE_URL,便于 7006 回调调通
|
||
set -e
|
||
cd "$(dirname "$0")"
|
||
|
||
if ! command -v ngrok >/dev/null 2>&1; then
|
||
echo "未检测到 ngrok。请先安装:"
|
||
echo " brew install ngrok/ngrok/ngrok # macOS"
|
||
echo " 或从 https://ngrok.com/download 下载"
|
||
exit 1
|
||
fi
|
||
|
||
NGROK_LOG="/tmp/ngrok-wechataiclaw.log"
|
||
rm -f "$NGROK_LOG"
|
||
|
||
# 避免重复启动导致多个 ngrok
|
||
if curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4040/api/tunnels 2>/dev/null | grep -q 200; then
|
||
echo "检测到 ngrok 已在运行(4040 可访问),直接读取 URL..."
|
||
else
|
||
echo "启动 ngrok http 8000(后端需在 8000 端口)..."
|
||
nohup ngrok http 8000 --log=stdout > "$NGROK_LOG" 2>&1 &
|
||
NGROK_PID=$!
|
||
echo "等待 ngrok 就绪(最多 30 秒)..."
|
||
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30; do
|
||
if curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4040/api/tunnels 2>/dev/null | grep -q 200; then
|
||
break
|
||
fi
|
||
sleep 1
|
||
done
|
||
sleep 1
|
||
fi
|
||
|
||
# 方式一:从 4040 API 获取
|
||
PAYLOAD=""
|
||
for _ in 1 2 3 4 5; do
|
||
PAYLOAD=$(curl -s http://127.0.0.1:4040/api/tunnels 2>/dev/null || true)
|
||
if [ -n "$PAYLOAD" ] && [ "$PAYLOAD" != "null" ]; then
|
||
break
|
||
fi
|
||
sleep 2
|
||
done
|
||
|
||
# 仅接受隧道公网地址,排除 dashboard/signup/get-started/docs 等说明页
|
||
_is_tunnel_url() {
|
||
case "$1" in
|
||
*dashboard*|*signup*|*get-started*|*ngrok.com/docs*|*your-authtoken*) return 1 ;;
|
||
*) return 0 ;;
|
||
esac
|
||
}
|
||
|
||
# 方式二:4040 不可用时从 ngrok 启动日志中解析 https 公网地址(不依赖本地 API)
|
||
if [ -z "$PAYLOAD" ] || [ "$PAYLOAD" = "null" ]; then
|
||
echo "4040 API 不可用,尝试从 ngrok 日志解析 URL(约 20 秒)..."
|
||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||
sleep 2
|
||
if [ -f "$NGROK_LOG" ] && [ -s "$NGROK_LOG" ]; then
|
||
# 只匹配隧道域名:*.ngrok-free.app / *.ngrok.io(排除 dashboard.ngrok.com 等)
|
||
PUBLIC_URL_FROM_LOG=$(grep -oE 'https://[a-zA-Z0-9][-a-zA-Z0-9.]*\.(ngrok-free\.app|ngrok\.io|ngrok-app\.com)([^"'\''<> /]*|$)' "$NGROK_LOG" 2>/dev/null | head -1) || true
|
||
[ -z "$PUBLIC_URL_FROM_LOG" ] && PUBLIC_URL_FROM_LOG=$(grep -iE 'forwarding|Forwarding' "$NGROK_LOG" 2>/dev/null | grep -oE 'https://[a-zA-Z0-9][-a-zA-Z0-9.]*\.(ngrok-free\.app|ngrok\.io)[^ ]*' | head -1) || true
|
||
if [ -n "$PUBLIC_URL_FROM_LOG" ] && _is_tunnel_url "$PUBLIC_URL_FROM_LOG"; then
|
||
PUBLIC_URL="$PUBLIC_URL_FROM_LOG"
|
||
echo "已从日志解析到: $PUBLIC_URL"
|
||
break
|
||
fi
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# 若尚未从日志得到 URL,则从 API 的 PAYLOAD 解析
|
||
if [ -z "$PUBLIC_URL" ] && [ -n "$PAYLOAD" ] && [ "$PAYLOAD" != "null" ]; then
|
||
PUBLIC_URL=$(echo "$PAYLOAD" | python3 -c "
|
||
import sys, json
|
||
try:
|
||
d = json.load(sys.stdin)
|
||
tunnels = d.get('tunnels') if isinstance(d, dict) else (d if isinstance(d, list) else [])
|
||
if not isinstance(tunnels, list): tunnels = []
|
||
for t in tunnels:
|
||
if not isinstance(t, dict): continue
|
||
u = (t.get('public_url') or t.get('PublicURL') or '').strip()
|
||
if u.startswith('https://'):
|
||
print(u.rstrip('/'))
|
||
break
|
||
else:
|
||
if tunnels:
|
||
u = (tunnels[0].get('public_url') or tunnels[0].get('PublicURL') or '').strip()
|
||
if u: print(u.rstrip('/'))
|
||
except Exception: pass
|
||
" 2>/dev/null)
|
||
[ -z "$PUBLIC_URL" ] && PUBLIC_URL=$(echo "$PAYLOAD" | grep -oE 'https://[a-zA-Z0-9][-a-zA-Z0-9.]*\.(ngrok-free\.app|ngrok\.io|ngrok-app\.com)[^"]*' | head -1 | sed 's|"$||')
|
||
[ -z "$PUBLIC_URL" ] && PUBLIC_URL=$(echo "$PAYLOAD" | grep -oE '"public_url"\s*:\s*"https://[^"]+' | sed 's/.*"https:/https:/' | sed 's/"$//' | head -1)
|
||
fi
|
||
|
||
if [ -z "$PUBLIC_URL" ]; then
|
||
if [ -f "$NGROK_LOG" ] && grep -qE 'authentication failed|ERR_NGROK_4018|authtoken|requires a verified account' "$NGROK_LOG" 2>/dev/null; then
|
||
echo "ngrok 需要先配置 authtoken(未登录或 token 未配置)。"
|
||
echo "请到 https://dashboard.ngrok.com/get-started/your-authtoken 获取 token,然后执行:"
|
||
echo " ngrok config add-authtoken <你的token>"
|
||
echo "再重新运行: ./run-ngrok.sh"
|
||
else
|
||
echo "解析 ngrok URL 失败。请手动运行: ngrok http 8000"
|
||
echo "把终端里显示的 https 隧道地址写入 .env: CALLBACK_BASE_URL=https://xxxx.ngrok-free.app"
|
||
echo "或查看日志: cat $NGROK_LOG"
|
||
fi
|
||
exit 1
|
||
fi
|
||
|
||
echo "ngrok 公网地址: $PUBLIC_URL"
|
||
|
||
# 更新 .env:存在 CALLBACK_BASE_URL 则替换,否则追加
|
||
ENV_FILE=".env"
|
||
if [ ! -f "$ENV_FILE" ]; then
|
||
echo "CALLBACK_BASE_URL=$PUBLIC_URL" >> "$ENV_FILE"
|
||
echo "已写入 $ENV_FILE: CALLBACK_BASE_URL=$PUBLIC_URL"
|
||
else
|
||
if grep -q '^CALLBACK_BASE_URL=' "$ENV_FILE" 2>/dev/null; then
|
||
if [[ "$(uname)" == "Darwin" ]]; then
|
||
sed -i '' "s|^CALLBACK_BASE_URL=.*|CALLBACK_BASE_URL=$PUBLIC_URL|" "$ENV_FILE"
|
||
else
|
||
sed -i "s|^CALLBACK_BASE_URL=.*|CALLBACK_BASE_URL=$PUBLIC_URL|" "$ENV_FILE"
|
||
fi
|
||
echo "已更新 $ENV_FILE: CALLBACK_BASE_URL=$PUBLIC_URL"
|
||
else
|
||
echo "" >> "$ENV_FILE"
|
||
echo "# 消息回调(ngrok 调通用,由 run-ngrok.sh 自动写入)" >> "$ENV_FILE"
|
||
echo "CALLBACK_BASE_URL=$PUBLIC_URL" >> "$ENV_FILE"
|
||
echo "已追加 $ENV_FILE: CALLBACK_BASE_URL=$PUBLIC_URL"
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
echo "下一步:"
|
||
echo " 1. 若尚未启动后端,请在新终端执行: ./run-dev.sh"
|
||
echo " 2. 后端启动时会向 7006 注册 SetCallback,回调地址: $PUBLIC_URL/api/callback/wechat-message"
|
||
echo " 3. 访问管理页 http://localhost:3000 登录后,新消息会由 7006 POST 到上述地址"
|
||
echo ""
|
||
echo "(本终端可保持 ngrok 运行;或先 Ctrl+C 结束 ngrok,再按上述步骤先 run-ngrok.sh 再 run-dev.sh)"
|