feat: sync bot setup and webhook diagnostics

This commit is contained in:
A 2026-07-22 00:51:52 +08:00
parent f53579416e
commit d9875b5caa
11 changed files with 521 additions and 29 deletions

View file

@ -77,7 +77,117 @@
| `TELESRV_TELEGRAM_LOGIN_SWEEP_INTERVAL` | duration / `5m` | retention worker 周期,限定 `10s..1h`。 |
| `TELESRV_TELEGRAM_LOGIN_SWEEP_BATCH` | int / `500` | 每轮最大清理行数,限定 `1..1000`。 |
### 3.1 Telegram Login / OIDC 完整启用流程
### 3.1 Bot API webhook 故障排查
先区分三个地址,禁止把 webhook 接收域名当成 Bot API 地址:
| 名称 | 配置/来源 | 方向与用途 |
|---|---|---|
| Bot API listener | telesrv 的 `TELESRV_BOT_API_ADDR` | telesrv 的监听地址;空值表示关闭。`0.0.0.0` 只能用于 bind不能作为客户端请求目标。 |
| Bot API base URL | bot 应用的 `TELEGRAM_API_URL` 等配置 | bot 应用访问 telesrv 的可达地址,例如 `http://172.17.0.1:8088`。方法地址为 `<base>/bot<TOKEN>/<method>`,文件地址为 `<base>/file/bot<TOKEN>/<file_path>`。 |
| Webhook receiver URL | bot 应用的 `WEBHOOK_URL + WEBHOOK_PATH`,经 `setWebhook` 登记 | telesrv 主动 POST update 的目标,例如 `https://bot.example.com/webhook`。它不是 Bot API base URL。 |
网络方向也不同polling 是 `bot 应用 -> telesrv Bot API`webhook 是
`telesrv -> bot 应用 webhook receiver`。因此 polling 正常只能证明前一条路径可达,
不能证明 webhook 的 DNS、出站 TCP、TLS、反向代理或 Docker hairpin 路径正常。
#### 1. 从 Bot API 查询真实 webhook 状态
应在 bot 应用容器中使用它实际配置的 Bot API base URL不要把 token 展开后粘贴到
聊天、工单或截图:
```sh
curl -sS -X POST \
"${TELEGRAM_API_URL%/}/bot${BOT_TOKEN}/getWebhookInfo" | jq
```
若没有 `TELEGRAM_API_URL` 这个变量,就把它替换成与
`TELESRV_BOT_API_ADDR` 对应的**客户端可达地址**。例如 telesrv 监听
`0.0.0.0:8088`,同宿主 Docker 容器可能使用 `http://172.17.0.1:8088`;不要请求
`http://0.0.0.0:8088`
按下表判读响应:
| 结果 | 结论与下一步 |
|---|---|
| `url` 为空 | webhook 没有登记到这台 telesrv检查 bot 应用是否确实使用该 Bot API base URL以及启动时 `setWebhook` 是否成功。 |
| `pending_update_count` 增长 | update 已进入 telesrv durable queue但没有成功交付继续看 `last_error_message`。 |
| `last_error_message` 为 HTTP `401`/`403` | 接收端已可达,但 webhook secret 不一致或请求被认证层拒绝。 |
| `dial tcp ... i/o timeout` | telesrv 到目标 IP/端口的连接超时检查出站防火墙、Docker 网络、回环 NAT/hairpin 和安全组。 |
| `connection refused` | 目标地址可达,但相应端口没有监听或端口映射/反代 upstream 错误。 |
| DNS/`no such host` | telesrv 所在运行环境无法解析 webhook hostname。 |
| TLS/`x509` 错误 | 证书链、hostname、SNI 或容器 CA trust 有问题。HTTPS 使用系统信任链。 |
| `allowed_updates` 不含目标类型 | 新产生的该类型 update 不会入队;普通 `/start` 至少需要 `message`。 |
| pending 归零但应用无响应 | telesrv 已收到 2xx转查接收应用内部 queue、worker、dispatcher 和 handler 日志。 |
`getWebhookInfo` 查询的是 telesrv 持久化的交付事实;应用自己的 `/health` 只能证明
接收路由和 worker 已启动,不能代替这一步。
#### 2. 用正确请求头验证接收端
Telegram webhook secret 与 Bot token、OIDC Client Secret、API key 都是不同凭据。
接收端校验的标准请求头是 `X-Telegram-Bot-Api-Secret-Token`,不是
`Authorization: Bearer`
```sh
curl -i -X POST "${WEBHOOK_URL%/}${WEBHOOK_PATH}" \
-H 'Content-Type: application/json' \
-H "X-Telegram-Bot-Api-Secret-Token: ${WEBHOOK_SECRET_TOKEN}" \
-d '{"update_id":2147483000}'
```
预期为 HTTP 2xx。`401 invalid_secret_token` 表示请求已经到达应用,但 header 缺失或
值不匹配。编辑 `.env` 后必须重建/重启读取该配置的应用;只修改磁盘文件不会更新
已经登记到 telesrv 的 secret也不会更新接收进程启动时捕获的 secret。
#### 3. 从 telesrv 的实际网络命名空间测试
浏览器或官方 Telegram 能访问公网 webhook只能证明公网入站正常。必须从实际运行
telesrv 的宿主机、容器或 network namespace 再测一次:
```sh
docker exec <telesrv-container> sh -lc \
'getent hosts bot.example.com; curl -vk --connect-timeout 10 https://bot.example.com/health/unified'
```
如果公网客户端正常而这里 `dial tcp ...:443: i/o timeout`,常见原因是同机公网 IP
回环失败。优先使用 split DNS 或容器 host mapping让公网 hostname 在 telesrv 容器
内解析到反向代理的内部入口,同时保留原 hostname、HTTPS SNI 和证书校验。例如反代
的 443 已发布到 Docker 宿主机时,可先验证:
```sh
curl -vk --resolve bot.example.com:443:172.17.0.1 \
https://bot.example.com/health/unified
```
验证通过后,可在 telesrv Compose 中使用与实际网络匹配的配置:
```yaml
extra_hosts:
- "bot.example.com:host-gateway"
```
其它可选修复包括:把 telesrv 接入反向代理所在 Docker network、为 Docker subnet
放行宿主机 443或修正云安全组/NAT hairpin。telesrv 允许登记内部 HTTP receiver
但只有在两端共享受控内网且调用方的 `WEBHOOK_URL` 不同时承担 OIDC、支付或公开媒体
回调时才应使用;不要为绕过网络问题盲目把应用的全局公开 URL 改成内部地址。
#### 4. 修复后的闭环验证
1. 重新启动 bot 应用,让它用当前 URL、secret 和 `allowed_updates` 再次调用
`setWebhook`
2. 发送一条新的 `/start` 或点击 callback 按钮。
3. 再次调用 `getWebhookInfo``pending_update_count` 应下降到 `0`,且不再出现新的
`last_error_date`
4. 检查 telesrv Warning 日志中的 `bot api webhook delivery failed`。日志包含
`bot_user_id``retry_in` 和失败原因,但不得记录 webhook URL、Bot token 或 secret。
5. 检查接收应用是否记录并处理该 `update_id`。webhook 是 at-least-once应用必须能
安全处理失败重试带来的重复 update。
若凭据曾出现在命令历史、聊天或截图中,立即轮换 Bot token、webhook secret、OIDC
Client Secret 及同屏暴露的其它 API key/数据库密码;排查资料只保留脱敏结果。
### 3.2 Telegram Login / OIDC 完整启用流程
#### 1. 一次性生成 `data/telegram-login`
@ -151,7 +261,9 @@ discovery 返回的 `issuer` 必须等于配置值,`authorization_endpoint`、
- `Client ID`bot user ID 的十进制字符串;
- `Client Secret`:只显示一次,与 Bot API token 不同,必须立即保存到密钥管理系统。
接着逐条发送配置命令。下面假设依赖方页面运行在 `http://192.0.2.30:3000`
选择一次 bot 后会持续停留在它的配置会话中,无需为每项修改重复 `/setlogin` 和 bot
username。可以逐条发送也可以像下面这样在一条消息中粘贴多行命令每条消息最多
32 行)。下面假设依赖方页面运行在 `http://192.0.2.30:3000`
```text
add origin http://192.0.2.30:3000
@ -160,6 +272,11 @@ algorithm RS256
enable
```
全部修改成功后发送 `/done`BotFather 会退出配置会话并返回最终配置摘要。各条修改会
立即生效;`/cancel` 只关闭当前会话,不会回滚已经成功的修改。多行消息若中途失败,
BotFather 会明确列出已应用项、失败行以及未执行的后续行,并保留当前 bot 选择供修正
后继续操作。
`origin` 只能是无 path/query/fragment 的精确 Web origin用于 JS SDK、popup CORS 和
legacy `login_url``redirect` 是 Authorization Code Flow 返回 code 的精确完整 URI。
不支持 wildcard 或 prefix 匹配。用 `/logininfo` 检查状态和登记值;用 `/setlogin`