Files
wechatAiclaw/run-ngrok.sh
丹尼尔 8b62c445fc fix:bug
2026-03-12 18:42:23 +08:00

149 lines
6.3 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
# 用 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 时默认不覆盖,避免每次重启都要重新设定;传 --update 则强制更新
FORCE_UPDATE=0
for a in "$@"; do
[ "$a" = "--update" ] && FORCE_UPDATE=1
done
ENV_FILE=".env"
CURRENT_CB=""
[ -f "$ENV_FILE" ] && CURRENT_CB=$(grep -E '^CALLBACK_BASE_URL=' "$ENV_FILE" 2>/dev/null | sed 's/^CALLBACK_BASE_URL=//' | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') || true
if [ "$FORCE_UPDATE" = "1" ] || [ -z "$CURRENT_CB" ]; then
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
else
echo "已保留现有 CALLBACK_BASE_URL=$CURRENT_CB(不覆盖)。若 ngrok 已换新地址,请执行: ./run-ngrok.sh --update"
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"