diff --git a/.env.example b/.env.example index 8ce56a2..2474423 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,9 @@ LX_DB_PASSWORD=replace-with-strong-database-password LX_JWT_SECRET=replace-with-at-least-32-random-characters LX_WEB_PORT=8088 +DEBIAN_MIRROR=https://deb.debian.org/debian +DEBIAN_SECURITY_MIRROR=https://deb.debian.org/debian-security +FFMPEG_BINARIES_URL=https://github.com/eugeneware/ffmpeg-static/releases/download # DeepSeek Anthropic-compatible API DEEPSEEK_API_KEY=replace-with-rotated-deepseek-key @@ -20,6 +23,8 @@ TELEGRAM_WEBHOOK_SECRET=replace-with-random-webhook-secret WHATSAPP_GATEWAY_TOKEN=replace-with-at-least-32-random-characters WHATSAPP_WEBHOOK_SECRET=replace-with-another-32-random-secret WHATSAPP_LOG_LEVEL=info +# 可选。只影响 WhatsApp 网关出站,不设置系统或平台全局代理。 +WHATSAPP_DEFAULT_PROXY_URL= PUBLIC_WEBHOOK_BASE_URL=http://host.docker.internal:8088 APP_PUBLIC_URL=http://127.0.0.1:8088 diff --git a/.gitignore b/.gitignore index 052f9ef..d3aa888 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ storage/ *.sqlite3 *.dump *.sql +!telegram-gateway/docker/scripts/init-db.sql *.rdb *.binlog diff --git a/CHANGELOG.md b/CHANGELOG.md index c0f9085..e8dcd3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ 本项目遵循语义化版本。早期版本由经过脱敏的本地源码快照恢复,因此使用带日期的 `snapshot-*` 标签;自 `v1.0.0` 起按正式发布流程维护。 +## [Unreleased] + +### 新增 + +- Telegram Gateway 支持环境级默认 SOCKS5/HTTP/MTProto 代理,账号专属绑定仍具有更高优先级。 +- WhatsApp Gateway 支持环境级默认 HTTP/HTTPS 代理,账号专属代理仍可覆盖默认值。 +- 默认代理仅作用于两个消息网关,不改变平台、邮箱、数据库或 Docker 的全局网络出口。 +- Docker 构建支持可配置的 Debian 与 FFmpeg 下载镜像,适配受限网络环境。 + +### 修复 + +- 将 Telegram Gateway 首次启动所需的 PostgreSQL 初始化脚本纳入版本控制,避免空数据库因挂载目录而启动失败。 +- Telegram Gateway 本机调试端口改为 `127.0.0.1:3010`,避免与 Gitea 的 `3000` 端口冲突并阻止公网直连管理接口。 + ## [1.1.0] - 2026-08-01 ### 新增 diff --git a/Dockerfile b/Dockerfile index 355a349..137c8ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ FROM node:20-alpine AS web-build +ARG FFMPEG_BINARIES_URL=https://github.com/eugeneware/ffmpeg-static/releases/download WORKDIR /app COPY package*.json ./ RUN npm ci diff --git a/docker-compose.yml b/docker-compose.yml index 5809e66..e74fc20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,10 +19,14 @@ services: whatsapp-gateway: build: context: ./whatsapp-gateway + args: + DEBIAN_MIRROR: ${DEBIAN_MIRROR:-https://deb.debian.org/debian} + DEBIAN_SECURITY_MIRROR: ${DEBIAN_SECURITY_MIRROR:-https://deb.debian.org/debian-security} environment: PORT: 3200 DATA_DIR: /data WHATSAPP_GATEWAY_TOKEN: ${WHATSAPP_GATEWAY_TOKEN:?WHATSAPP_GATEWAY_TOKEN is required} + WHATSAPP_DEFAULT_PROXY_URL: ${WHATSAPP_DEFAULT_PROXY_URL:-} LOG_LEVEL: ${WHATSAPP_LOG_LEVEL:-info} volumes: - lingxi_whatsapp_data:/data @@ -89,6 +93,8 @@ services: build: context: . dockerfile: Dockerfile + args: + FFMPEG_BINARIES_URL: ${FFMPEG_BINARIES_URL:-https://github.com/eugeneware/ffmpeg-static/releases/download} ports: - "${LX_WEB_PORT:-8088}:80" depends_on: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index e8c9795..ccac9b9 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -37,6 +37,24 @@ docker compose logs --tail=100 api whatsapp-gateway web 平台的 `TELEGRAM_GATEWAY_URL` 指向网关 `/api` 地址。生产环境应通过私有网络连接,不把 Gateway 管理接口直接暴露公网。详细配置见 [Telegram Gateway 接入指南](TELEGRAM-GATEWAY.md)。 +### 2.1 仅消息网关使用出站代理 + +生产环境可以创建一个不映射宿主机端口的专用 Docker 网络和 V2Ray/Xray 容器,并只将 Telegram、WhatsApp 网关接入该网络。不要设置 Docker Daemon、宿主机或平台 API 的全局 `HTTP_PROXY`/`HTTPS_PROXY`。 + +```env +# Telegram Gateway +TG_DEFAULT_PROXY_TYPE=socks5 +TG_DEFAULT_PROXY_HOST=lingxi-egress +TG_DEFAULT_PROXY_PORT=10808 + +# 平台 WhatsApp Gateway +WHATSAPP_DEFAULT_PROXY_URL=http://lingxi-egress:10809 +``` + +账号级代理优先于上述默认代理。代理容器应使用 `internal/external Docker network + no host ports` 的方式隔离;配置文件和认证信息仅保存在服务器受限目录,不进入 Git。上线前至少验证 Telegram API 与 WhatsApp Web 的 HTTPS 连接,并确认 API、数据库、邮箱和 AI 服务未配置全局代理变量。 + +若部署网络无法访问 Debian 官方源或 FFmpeg GitHub Release,可通过 `.env` 的 `DEBIAN_MIRROR`、`DEBIAN_SECURITY_MIRROR` 和 `FFMPEG_BINARIES_URL` 指定构建镜像;这些变量只影响镜像构建,不改变运行时出口。 + ## 3. 生产入口 在 Compose 外层部署 Caddy、Nginx 或云负载均衡: @@ -49,6 +67,8 @@ docker compose logs --tail=100 api whatsapp-gateway web 将 `APP_PUBLIC_URL` 和 `PUBLIC_WEBHOOK_BASE_URL` 改为正式 HTTPS 地址,随后重建 API。 +当前生产拓扑使用 Caddy 与 NPS 时,应在 NPS HTTP Host 中将域名指向平台内网 `8088`,Caddy 只反向代理到 NPS 的本机 HTTP 入口。修改前分别备份 NPS Host 配置和 Caddyfile,验证配置后再重启 NPS、平滑重载 Caddy。 + ## 4. 更新 ```bash diff --git a/docs/TELEGRAM-GATEWAY.md b/docs/TELEGRAM-GATEWAY.md index bcb8759..c2ff256 100644 --- a/docs/TELEGRAM-GATEWAY.md +++ b/docs/TELEGRAM-GATEWAY.md @@ -18,6 +18,7 @@ Copy-Item telegram-gateway/.env.example telegram-gateway/.env - `JWT_SECRET`、`ADMIN_API_KEY`:分别生成强随机值,不得复用。 - `DB_PASSWORD`:生产数据库强密码。 - `CORS_ORIGINS`:仅填写受信任的管理入口;平台服务端调用不依赖浏览器跨域。 +- `TG_DEFAULT_PROXY_*`:可选的默认 TDLib 代理;账号绑定的专属代理优先。 生产环境不要使用示例中的 `postgres` 密码。`.env` 不得提交 Git。 diff --git a/telegram-gateway/.env.example b/telegram-gateway/.env.example index c89648e..db13fc1 100644 --- a/telegram-gateway/.env.example +++ b/telegram-gateway/.env.example @@ -1,5 +1,8 @@ # Application NODE_ENV=development +# Optional image build mirrors for networks where deb.debian.org is unavailable. +DEBIAN_MIRROR=https://deb.debian.org/debian +DEBIAN_SECURITY_MIRROR=https://deb.debian.org/debian-security PORT=3000 APP_NAME=TelegramUserGateway CORS_ORIGINS= @@ -29,6 +32,14 @@ TDLIB_PATH=/usr/local/lib/libtdjson.so TG_API_ID=0 TG_API_HASH=your_api_hash_here +# 可选默认代理。账号未绑定专属代理时使用;只影响 Telegram TDLib 出站。 +TG_DEFAULT_PROXY_TYPE= +TG_DEFAULT_PROXY_HOST= +TG_DEFAULT_PROXY_PORT= +TG_DEFAULT_PROXY_USERNAME= +TG_DEFAULT_PROXY_PASSWORD= +TG_DEFAULT_PROXY_SECRET= + # Rate Limiting RATE_LIMIT_DEFAULT_TTL=60000 RATE_LIMIT_DEFAULT_LIMIT=30 diff --git a/telegram-gateway/docker/Dockerfile b/telegram-gateway/docker/Dockerfile index 52498b4..a6827e5 100644 --- a/telegram-gateway/docker/Dockerfile +++ b/telegram-gateway/docker/Dockerfile @@ -1,5 +1,8 @@ FROM node:20-bookworm-slim AS builder +ARG DEBIAN_MIRROR=https://deb.debian.org/debian +ARG DEBIAN_SECURITY_MIRROR=https://deb.debian.org/debian-security +RUN sed -i "s|http://deb.debian.org/debian-security|${DEBIAN_SECURITY_MIRROR}|g; s|http://deb.debian.org/debian|${DEBIAN_MIRROR}|g" /etc/apt/sources.list.d/debian.sources RUN apt-get update \ && apt-get install -y --no-install-recommends g++ make python3 \ && rm -rf /var/lib/apt/lists/* @@ -22,6 +25,9 @@ RUN npm prune --omit=dev FROM node:20-bookworm-slim +ARG DEBIAN_MIRROR=https://deb.debian.org/debian +ARG DEBIAN_SECURITY_MIRROR=https://deb.debian.org/debian-security +RUN sed -i "s|http://deb.debian.org/debian-security|${DEBIAN_SECURITY_MIRROR}|g; s|http://deb.debian.org/debian|${DEBIAN_MIRROR}|g" /etc/apt/sources.list.d/debian.sources RUN apt-get update \ && apt-get install -y --no-install-recommends tini ca-certificates \ && rm -rf /var/lib/apt/lists/* diff --git a/telegram-gateway/docker/docker-compose.yml b/telegram-gateway/docker/docker-compose.yml index 6c9e427..cce0f55 100644 --- a/telegram-gateway/docker/docker-compose.yml +++ b/telegram-gateway/docker/docker-compose.yml @@ -30,9 +30,12 @@ services: build: context: .. dockerfile: docker/Dockerfile + args: + DEBIAN_MIRROR: ${DEBIAN_MIRROR:-https://deb.debian.org/debian} + DEBIAN_SECURITY_MIRROR: ${DEBIAN_SECURITY_MIRROR:-https://deb.debian.org/debian-security} container_name: tgw-app ports: - - '3000:3000' + - '127.0.0.1:3010:3000' env_file: - ../.env environment: @@ -52,6 +55,12 @@ services: TDLIB_PATH: /app/node_modules/@prebuilt-tdlib/linux-x64-glibc/libtdjson.so TG_API_ID: ${TG_API_ID} TG_API_HASH: ${TG_API_HASH} + TG_DEFAULT_PROXY_TYPE: ${TG_DEFAULT_PROXY_TYPE:-} + TG_DEFAULT_PROXY_HOST: ${TG_DEFAULT_PROXY_HOST:-} + TG_DEFAULT_PROXY_PORT: ${TG_DEFAULT_PROXY_PORT:-} + TG_DEFAULT_PROXY_USERNAME: ${TG_DEFAULT_PROXY_USERNAME:-} + TG_DEFAULT_PROXY_PASSWORD: ${TG_DEFAULT_PROXY_PASSWORD:-} + TG_DEFAULT_PROXY_SECRET: ${TG_DEFAULT_PROXY_SECRET:-} depends_on: postgres: condition: service_healthy diff --git a/telegram-gateway/docker/scripts/init-db.sql b/telegram-gateway/docker/scripts/init-db.sql new file mode 100644 index 0000000..0a00220 --- /dev/null +++ b/telegram-gateway/docker/scripts/init-db.sql @@ -0,0 +1,8 @@ +-- Telegram User Gateway - Database Initialization +-- This script runs on first PostgreSQL startup + +-- Enable UUID extension +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Create indexes will be handled by TypeORM synchronize in dev +-- or by migrations in production diff --git a/telegram-gateway/src/modules/tg-accounts/tg-accounts.service.ts b/telegram-gateway/src/modules/tg-accounts/tg-accounts.service.ts index 24144fd..13e797f 100644 --- a/telegram-gateway/src/modules/tg-accounts/tg-accounts.service.ts +++ b/telegram-gateway/src/modules/tg-accounts/tg-accounts.service.ts @@ -171,7 +171,7 @@ export class TgAccountsService implements OnModuleInit { const binding = await this.bindingRepo.findOne({ where: { accountId, isActive: true }, }); - if (!binding) return undefined; + if (!binding) return this.getDefaultProxy(); const proxy = await this.proxyRepo .createQueryBuilder('proxy') @@ -179,7 +179,7 @@ export class TgAccountsService implements OnModuleInit { .where('proxy.id = :id', { id: binding.proxyId }) .andWhere('proxy.isActive = :isActive', { isActive: true }) .getOne(); - if (!proxy) return undefined; + if (!proxy) return this.getDefaultProxy(); return { type: proxy.type, @@ -190,4 +190,27 @@ export class TgAccountsService implements OnModuleInit { secret: proxy.secret, }; } + + private getDefaultProxy() { + const host = String(process.env.TG_DEFAULT_PROXY_HOST || '').trim(); + const port = Number(process.env.TG_DEFAULT_PROXY_PORT || 0); + if (!host || !Number.isInteger(port) || port < 1 || port > 65535) { + return undefined; + } + + const type = String(process.env.TG_DEFAULT_PROXY_TYPE || 'socks5').toLowerCase(); + if (!['socks5', 'http', 'mtproto'].includes(type)) { + this.logger.warn(`Ignoring unsupported TG_DEFAULT_PROXY_TYPE: ${type}`); + return undefined; + } + + return { + type, + host, + port, + username: process.env.TG_DEFAULT_PROXY_USERNAME || '', + password: process.env.TG_DEFAULT_PROXY_PASSWORD || '', + secret: process.env.TG_DEFAULT_PROXY_SECRET || '', + }; + } } diff --git a/whatsapp-gateway/Dockerfile b/whatsapp-gateway/Dockerfile index 0234eec..bd6d4e0 100644 --- a/whatsapp-gateway/Dockerfile +++ b/whatsapp-gateway/Dockerfile @@ -1,5 +1,9 @@ FROM node:20-bookworm-slim +ARG DEBIAN_MIRROR=https://deb.debian.org/debian +ARG DEBIAN_SECURITY_MIRROR=https://deb.debian.org/debian-security +RUN sed -i "s|http://deb.debian.org/debian-security|${DEBIAN_SECURITY_MIRROR}|g; s|http://deb.debian.org/debian|${DEBIAN_MIRROR}|g" /etc/apt/sources.list.d/debian.sources + ENV NODE_ENV=production WORKDIR /app diff --git a/whatsapp-gateway/src/index.js b/whatsapp-gateway/src/index.js index 26b3257..3bd4aac 100644 --- a/whatsapp-gateway/src/index.js +++ b/whatsapp-gateway/src/index.js @@ -31,6 +31,7 @@ const accountsFile = path.join(dataDir, 'accounts.json') const webhookDeliveriesFile = path.join(dataDir, 'webhook-deliveries.json') const outboundJobsFile = path.join(dataDir, 'outbound-jobs.json') const gatewayToken = String(process.env.WHATSAPP_GATEWAY_TOKEN || '') +const defaultProxyUrl = String(process.env.WHATSAPP_DEFAULT_PROXY_URL || '').trim() const maxMessagesPerChat = Math.max(2_000, Number(process.env.MAX_MESSAGES_PER_CHAT || 20_000)) const log = pino({ level: process.env.LOG_LEVEL || 'info' }) @@ -90,7 +91,7 @@ function accountWebhookSecret(account) { } function accountProxyUrl(account) { - return decryptCredential(account?.proxyUrlEncrypted || '') + return decryptCredential(account?.proxyUrlEncrypted || '') || defaultProxyUrl } function jsonSafe(value) {