This commit is contained in:
丹尼尔
2026-03-11 18:19:30 +08:00
parent 152877cef2
commit b7ef2569c4
13 changed files with 6907 additions and 94 deletions

144
run-ngrok.sh Executable file
View File

@@ -0,0 +1,144 @@
#!/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 端口,可先在本脚本之后另开终端运行 ./run-dev.sh..."
nohup ngrok http 8000 --log=stdout > "$NGROK_LOG" 2>&1 &
NGROK_PID=$!
echo "等待 ngrok 就绪(最多 30 秒,并从日志解析 URL..."
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)
if [ -z "$PUBLIC_URL" ]; then
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|"$||')
fi
if [ -z "$PUBLIC_URL" ]; then
PUBLIC_URL=$(echo "$PAYLOAD" | grep -oE '"public_url"\s*:\s*"https://[^"]+' | sed 's/.*"https:/https:/' | sed 's/"$//' | head -1)
fi
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"