feat(gateway): add scoped proxy egress deployment
This commit is contained in:
@@ -2,6 +2,9 @@
|
|||||||
LX_DB_PASSWORD=replace-with-strong-database-password
|
LX_DB_PASSWORD=replace-with-strong-database-password
|
||||||
LX_JWT_SECRET=replace-with-at-least-32-random-characters
|
LX_JWT_SECRET=replace-with-at-least-32-random-characters
|
||||||
LX_WEB_PORT=8088
|
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 Anthropic-compatible API
|
||||||
DEEPSEEK_API_KEY=replace-with-rotated-deepseek-key
|
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_GATEWAY_TOKEN=replace-with-at-least-32-random-characters
|
||||||
WHATSAPP_WEBHOOK_SECRET=replace-with-another-32-random-secret
|
WHATSAPP_WEBHOOK_SECRET=replace-with-another-32-random-secret
|
||||||
WHATSAPP_LOG_LEVEL=info
|
WHATSAPP_LOG_LEVEL=info
|
||||||
|
# 可选。只影响 WhatsApp 网关出站,不设置系统或平台全局代理。
|
||||||
|
WHATSAPP_DEFAULT_PROXY_URL=
|
||||||
|
|
||||||
PUBLIC_WEBHOOK_BASE_URL=http://host.docker.internal:8088
|
PUBLIC_WEBHOOK_BASE_URL=http://host.docker.internal:8088
|
||||||
APP_PUBLIC_URL=http://127.0.0.1:8088
|
APP_PUBLIC_URL=http://127.0.0.1:8088
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ storage/
|
|||||||
*.sqlite3
|
*.sqlite3
|
||||||
*.dump
|
*.dump
|
||||||
*.sql
|
*.sql
|
||||||
|
!telegram-gateway/docker/scripts/init-db.sql
|
||||||
*.rdb
|
*.rdb
|
||||||
*.binlog
|
*.binlog
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
本项目遵循语义化版本。早期版本由经过脱敏的本地源码快照恢复,因此使用带日期的 `snapshot-*` 标签;自 `v1.0.0` 起按正式发布流程维护。
|
本项目遵循语义化版本。早期版本由经过脱敏的本地源码快照恢复,因此使用带日期的 `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
|
## [1.1.0] - 2026-08-01
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
FROM node:20-alpine AS web-build
|
FROM node:20-alpine AS web-build
|
||||||
|
ARG FFMPEG_BINARIES_URL=https://github.com/eugeneware/ffmpeg-static/releases/download
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|||||||
@@ -19,10 +19,14 @@ services:
|
|||||||
whatsapp-gateway:
|
whatsapp-gateway:
|
||||||
build:
|
build:
|
||||||
context: ./whatsapp-gateway
|
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:
|
environment:
|
||||||
PORT: 3200
|
PORT: 3200
|
||||||
DATA_DIR: /data
|
DATA_DIR: /data
|
||||||
WHATSAPP_GATEWAY_TOKEN: ${WHATSAPP_GATEWAY_TOKEN:?WHATSAPP_GATEWAY_TOKEN is required}
|
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}
|
LOG_LEVEL: ${WHATSAPP_LOG_LEVEL:-info}
|
||||||
volumes:
|
volumes:
|
||||||
- lingxi_whatsapp_data:/data
|
- lingxi_whatsapp_data:/data
|
||||||
@@ -89,6 +93,8 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
FFMPEG_BINARIES_URL: ${FFMPEG_BINARIES_URL:-https://github.com/eugeneware/ffmpeg-static/releases/download}
|
||||||
ports:
|
ports:
|
||||||
- "${LX_WEB_PORT:-8088}:80"
|
- "${LX_WEB_PORT:-8088}:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -37,6 +37,24 @@ docker compose logs --tail=100 api whatsapp-gateway web
|
|||||||
|
|
||||||
平台的 `TELEGRAM_GATEWAY_URL` 指向网关 `/api` 地址。生产环境应通过私有网络连接,不把 Gateway 管理接口直接暴露公网。详细配置见 [Telegram Gateway 接入指南](TELEGRAM-GATEWAY.md)。
|
平台的 `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. 生产入口
|
## 3. 生产入口
|
||||||
|
|
||||||
在 Compose 外层部署 Caddy、Nginx 或云负载均衡:
|
在 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。
|
将 `APP_PUBLIC_URL` 和 `PUBLIC_WEBHOOK_BASE_URL` 改为正式 HTTPS 地址,随后重建 API。
|
||||||
|
|
||||||
|
当前生产拓扑使用 Caddy 与 NPS 时,应在 NPS HTTP Host 中将域名指向平台内网 `8088`,Caddy 只反向代理到 NPS 的本机 HTTP 入口。修改前分别备份 NPS Host 配置和 Caddyfile,验证配置后再重启 NPS、平滑重载 Caddy。
|
||||||
|
|
||||||
## 4. 更新
|
## 4. 更新
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Copy-Item telegram-gateway/.env.example telegram-gateway/.env
|
|||||||
- `JWT_SECRET`、`ADMIN_API_KEY`:分别生成强随机值,不得复用。
|
- `JWT_SECRET`、`ADMIN_API_KEY`:分别生成强随机值,不得复用。
|
||||||
- `DB_PASSWORD`:生产数据库强密码。
|
- `DB_PASSWORD`:生产数据库强密码。
|
||||||
- `CORS_ORIGINS`:仅填写受信任的管理入口;平台服务端调用不依赖浏览器跨域。
|
- `CORS_ORIGINS`:仅填写受信任的管理入口;平台服务端调用不依赖浏览器跨域。
|
||||||
|
- `TG_DEFAULT_PROXY_*`:可选的默认 TDLib 代理;账号绑定的专属代理优先。
|
||||||
|
|
||||||
生产环境不要使用示例中的 `postgres` 密码。`.env` 不得提交 Git。
|
生产环境不要使用示例中的 `postgres` 密码。`.env` 不得提交 Git。
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Application
|
# Application
|
||||||
NODE_ENV=development
|
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
|
PORT=3000
|
||||||
APP_NAME=TelegramUserGateway
|
APP_NAME=TelegramUserGateway
|
||||||
CORS_ORIGINS=
|
CORS_ORIGINS=
|
||||||
@@ -29,6 +32,14 @@ TDLIB_PATH=/usr/local/lib/libtdjson.so
|
|||||||
TG_API_ID=0
|
TG_API_ID=0
|
||||||
TG_API_HASH=your_api_hash_here
|
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 Limiting
|
||||||
RATE_LIMIT_DEFAULT_TTL=60000
|
RATE_LIMIT_DEFAULT_TTL=60000
|
||||||
RATE_LIMIT_DEFAULT_LIMIT=30
|
RATE_LIMIT_DEFAULT_LIMIT=30
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
FROM node:20-bookworm-slim AS builder
|
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 \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends g++ make python3 \
|
&& apt-get install -y --no-install-recommends g++ make python3 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
@@ -22,6 +25,9 @@ RUN npm prune --omit=dev
|
|||||||
|
|
||||||
FROM node:20-bookworm-slim
|
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 \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends tini ca-certificates \
|
&& apt-get install -y --no-install-recommends tini ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -30,9 +30,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: docker/Dockerfile
|
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
|
container_name: tgw-app
|
||||||
ports:
|
ports:
|
||||||
- '3000:3000'
|
- '127.0.0.1:3010:3000'
|
||||||
env_file:
|
env_file:
|
||||||
- ../.env
|
- ../.env
|
||||||
environment:
|
environment:
|
||||||
@@ -52,6 +55,12 @@ services:
|
|||||||
TDLIB_PATH: /app/node_modules/@prebuilt-tdlib/linux-x64-glibc/libtdjson.so
|
TDLIB_PATH: /app/node_modules/@prebuilt-tdlib/linux-x64-glibc/libtdjson.so
|
||||||
TG_API_ID: ${TG_API_ID}
|
TG_API_ID: ${TG_API_ID}
|
||||||
TG_API_HASH: ${TG_API_HASH}
|
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:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -171,7 +171,7 @@ export class TgAccountsService implements OnModuleInit {
|
|||||||
const binding = await this.bindingRepo.findOne({
|
const binding = await this.bindingRepo.findOne({
|
||||||
where: { accountId, isActive: true },
|
where: { accountId, isActive: true },
|
||||||
});
|
});
|
||||||
if (!binding) return undefined;
|
if (!binding) return this.getDefaultProxy();
|
||||||
|
|
||||||
const proxy = await this.proxyRepo
|
const proxy = await this.proxyRepo
|
||||||
.createQueryBuilder('proxy')
|
.createQueryBuilder('proxy')
|
||||||
@@ -179,7 +179,7 @@ export class TgAccountsService implements OnModuleInit {
|
|||||||
.where('proxy.id = :id', { id: binding.proxyId })
|
.where('proxy.id = :id', { id: binding.proxyId })
|
||||||
.andWhere('proxy.isActive = :isActive', { isActive: true })
|
.andWhere('proxy.isActive = :isActive', { isActive: true })
|
||||||
.getOne();
|
.getOne();
|
||||||
if (!proxy) return undefined;
|
if (!proxy) return this.getDefaultProxy();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: proxy.type,
|
type: proxy.type,
|
||||||
@@ -190,4 +190,27 @@ export class TgAccountsService implements OnModuleInit {
|
|||||||
secret: proxy.secret,
|
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 || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
FROM node:20-bookworm-slim
|
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
|
ENV NODE_ENV=production
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const accountsFile = path.join(dataDir, 'accounts.json')
|
|||||||
const webhookDeliveriesFile = path.join(dataDir, 'webhook-deliveries.json')
|
const webhookDeliveriesFile = path.join(dataDir, 'webhook-deliveries.json')
|
||||||
const outboundJobsFile = path.join(dataDir, 'outbound-jobs.json')
|
const outboundJobsFile = path.join(dataDir, 'outbound-jobs.json')
|
||||||
const gatewayToken = String(process.env.WHATSAPP_GATEWAY_TOKEN || '')
|
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 maxMessagesPerChat = Math.max(2_000, Number(process.env.MAX_MESSAGES_PER_CHAT || 20_000))
|
||||||
const log = pino({ level: process.env.LOG_LEVEL || 'info' })
|
const log = pino({ level: process.env.LOG_LEVEL || 'info' })
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ function accountWebhookSecret(account) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function accountProxyUrl(account) {
|
function accountProxyUrl(account) {
|
||||||
return decryptCredential(account?.proxyUrlEncrypted || '')
|
return decryptCredential(account?.proxyUrlEncrypted || '') || defaultProxyUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsonSafe(value) {
|
function jsonSafe(value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user