Files
sundynix-agentix/deploy/132-app/docker-compose.yml
T
Blizzard a1c68ec6b2 feat(auth): 微信 access_token 走中控服务器(腾讯云静态 IP 换 token)
隐患:gateway 换 access_token 时微信看到的是本地宽带出网 IP(106.58.232.43,
动态会变),一变白名单就失效、二维码建不出来(40164)。

按微信官方推荐的「中控服务器」架构解决:腾讯云静态 IP 统一换 token,gateway 拉取
使用。微信 IP 白名单只限制换 token 这一步,拿 token 建二维码不查 IP —— 所以
换 token 挪到中控、建二维码仍在 gateway 本地,白名单只填腾讯云 IP,永不失效。

- wechat.PullToken:从中控 HTTPS 端点拉 token(Bearer 密钥鉴权),坏响应/403/空
  token 一律报错不当成功;中控没给 expires_in 时按 7200 兜底。
- handler.accessToken:配了 WECHAT_TOKEN_URL 就只从中控拉、绝不自己 FetchAccessToken
  (微信要求单点刷新,多点各自换会互相顶掉 token);不配维持直连,零副作用可回退。
- compose:gateway 加 WECHAT_TOKEN_URL / WECHAT_TOKEN_SECRET(从宿主机 .env 注入)。
- deploy/wechat-token-zhongkong.md:腾讯云侧 cron 脚本 + nginx 配置 + 切换验证步骤。

本地验证(假中控 httptest + 真 gateway):建票时 Redis 缓存的是中控给的 token
(FAKE_TOKEN_FROM_ZHONGKONG),gateway 未直连微信换 token;随后拿该 token 调
qrcode/create(假 token 报 40001 属预期)——证明「中控换 token → 本地建二维码」链路成立。
4 组 PullToken 单测覆盖正常/403/坏JSON/兜底。

真机验证(部署后):把本地 IP 从白名单删掉、只留腾讯云 IP,扫码仍能登录即坐实
qrcode 不受 IP 限制;若建二维码报 40164 则退回 tinyproxy 正向代理备选。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 09:53:27 +08:00

106 lines
5.7 KiB
YAML
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.
# sundynix-agentix · 应用机 192.168.100.132
#
# cd deploy/132-app && cp .env.example .env # 填密钥/密码/MinIO 凭据
# docker compose up -d --build # 在 132 本地构建并运行(Gitea 自动部署也走这条)
#
# 说明:
# - 对外只暴露 gateway:3000frp 映射)。gateway 已内嵌 admin 控制台(同源 serve UI+API),
# 无需单独 admin nginx 容器。dispatcher/mcp-go/mcp-py 只走 NATS,无对外端口。
# - 基础设施在 128PG/NATS集群/Redis/Milvus/Neo4j),对象存储在 126MinIO),env 全部指向它们。
# - 构建上下文为仓库根(../..,因 Go 服务 replace ../sundynix-shared)。
# - NATS_STREAM_REPLICAS=3durable 流走 128 三节点集群,有 Raft quorum 容错。
services:
gateway:
build: { context: ../.., dockerfile: sundynix-gateway/Dockerfile }
image: sundynix/gateway:${TAG:-latest}
restart: unless-stopped
environment:
APP_ENV: production
GATEWAY_ADDR: ":8080"
NATS_URL: nats://192.168.100.128:4222,nats://192.168.100.128:4223,nats://192.168.100.128:4224
NATS_STREAM_REPLICAS: "3"
POSTGRES_DSN: postgres://sundynix:${POSTGRES_PASSWORD:?}@192.168.100.128:5432/sundynix?sslmode=disable
REDIS_ADDR: 192.168.100.128:6379
MINIO_ENDPOINT: 192.168.100.126:9000
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?}
MINIO_BUCKET: ${MINIO_BUCKET:-sundynix-docs}
SUNDYNIX_SECRET_KEY: ${SUNDYNIX_SECRET_KEY:?}
JWT_SECRET: ${JWT_SECRET:?}
ADMIN_USER_IDS: ${ADMIN_USER_IDS:-}
CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN:-*}
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.100.128:4318
# 微信域名校验文件目录(容器内路径);未设则该路由不注册
WECHAT_VERIFY_DIR: /etc/sundynix/wechat-verify
# 微信 access_token 中控(腾讯云静态 IP 换 token,gateway 只拉不换)。
# 配了才从中控拉,避免本地动态出网 IP 触发 40164;不配则 gateway 直连微信换 token。
# 值放宿主机 .envWECHAT_TOKEN_URL=https://.../token.json、WECHAT_TOKEN_SECRET=<共享密钥>
WECHAT_TOKEN_URL: ${WECHAT_TOKEN_URL:-}
WECHAT_TOKEN_SECRET: ${WECHAT_TOKEN_SECRET:-}
ports: ["3000:8080"] # frp 外网 → 132:3000 → 容器 8080
volumes:
# 微信支付证书(商户私钥 + 微信支付公钥):宿主机 132 的目录只读挂进容器。
# ⚠️ admin「系统配置 → 支付」里填的路径必须是**容器内路径**/etc/sundynix/wechat-cert/...),
# 不是宿主机路径——容器看不到宿主机的 /home/workspace/...。私钥不进镜像、不进 git。
- /home/workspace/wechat-pay-cert:/etc/sundynix/wechat-cert:ro
# 微信域名校验文件(MP_verify_xxx.txt):配置 JS安全域名/网页授权域名时微信要求
# 能从域名根目录访问到。放宿主机、只读挂进来,不进镜像也不进 git。
- /home/workspace/wechat-verify:/etc/sundynix/wechat-verify:ro
dispatcher:
build: { context: ../.., dockerfile: sundynix-dispatcher/Dockerfile }
image: sundynix/dispatcher:${TAG:-latest}
restart: unless-stopped
environment:
APP_ENV: production
NATS_URL: nats://192.168.100.128:4222,nats://192.168.100.128:4223,nats://192.168.100.128:4224
NATS_STREAM_REPLICAS: "3"
SUNDYNIX_SECRET_KEY: ${SUNDYNIX_SECRET_KEY:?}
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.100.128:4318
DISPATCHER_CONCURRENCY: ${DISPATCHER_CONCURRENCY:-8}
TASK_TOKEN_BUDGET: ${TASK_TOKEN_BUDGET:-200000}
mcp-go:
build: { context: ../.., dockerfile: sundynix-mcp-go/Dockerfile }
image: sundynix/mcp-go:${TAG:-latest}
restart: unless-stopped
environment:
APP_ENV: production
NATS_URL: nats://192.168.100.128:4222,nats://192.168.100.128:4223,nats://192.168.100.128:4224
NATS_STREAM_REPLICAS: "3"
POSTGRES_DSN: postgres://sundynix:${POSTGRES_PASSWORD:?}@192.168.100.128:5432/sundynix?sslmode=disable
REDIS_ADDR: 192.168.100.128:6379
MILVUS_ADDR: 192.168.100.128:19530
NEO4J_URI: neo4j://192.168.100.128:7687
NEO4J_USER: neo4j
NEO4J_PASS: ${NEO4J_PASSWORD:?}
# 报告源/产物 + KB 正文对象存储(本会话新加 blob)——指向 126,勿漏,否则 fallback localhost 连不上。
MINIO_ENDPOINT: 192.168.100.126:9000
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?}
MINIO_BUCKET: ${MINIO_BUCKET:-sundynix-docs}
SUNDYNIX_SECRET_KEY: ${SUNDYNIX_SECRET_KEY:?}
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.100.128:4318
# 全文(bleve)索引落盘位置。不设则相对 CWD,写进容器可写层 → 每次发版重建容器就清零,
# 而混合检索只是静默少一路召回、不报错,很难发现。必须配合下面的持久卷。
BLEVE_PATH: /data/bleve
volumes:
- mcp-go-data:/data
mcp-py:
build: { context: ../../sundynix-mcp-py, dockerfile: Dockerfile }
image: sundynix/mcp-py:${TAG:-latest}
restart: unless-stopped
environment:
NATS_URL: nats://192.168.100.128:4222,nats://192.168.100.128:4223,nats://192.168.100.128:4224
# secure_sandbox(代码隔离执行)需访问宿主 Docker 拉一次性容器。安全敏感:等于把 132 宿主
# Docker 暴露给该服务(可逃逸);不需要代码执行工具可注释掉这两行。mcp-py 无对外端口。
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# 全文索引持久化:随容器重建保留(向量在 Milvus、图谱在 Neo4j,都在 128
# 只有 bleve 是跟着 mcp-go 走的本地索引,唯独它需要这个卷)。
volumes:
mcp-go-data: