176 lines
10 KiB
Markdown
176 lines
10 KiB
Markdown
# Nginx 公网入口(受控联调示例)
|
||
|
||
| 项 | 内容 |
|
||
| --- | --- |
|
||
| 目标 | 让 `agent.nianxx.com` 通过 HTTPS 访问本项目的兼容对话接口和 MCP |
|
||
| 状态 | 示例配置;公网机器、证书、网络白名单和真实鉴权仍待联调 |
|
||
| 上游 | 仅反代本机 `127.0.0.1:8080` |
|
||
|
||
## 1. 与旧配置的变化
|
||
|
||
旧配置把整个 `/` 转发到 DashScope,并在 Nginx 中比较 `xtoken`、注入 DashScope API Key。新边界是:
|
||
|
||
```text
|
||
公网 HTTPS
|
||
-> Nginx(TLS、精确路径、限流、SSE 传输设置)
|
||
-> 127.0.0.1:8080(fire-safety-ymd)
|
||
-> Go 侧校验 xtoken / MCP Bearer
|
||
```
|
||
|
||
Nginx 不再调用 DashScope,也不保存或注入 SuperAgent Open API Key、Chat `xtoken`、MCP Bearer 或数据库凭证。Go 服务负责把用户兼容请求转发到 SuperAgent,并负责 `/mcp` 的独立 Bearer 校验。这样可避免把 Secret 写入 Nginx 配置,也避免使用 Nginx `if` 比较 Secret。
|
||
|
||
## 2. 可复制的配置示例
|
||
|
||
配置文件位于 [`../../../deploy/nginx/fire-safety-ymd.conf.example`](../../../deploy/nginx/fire-safety-ymd.conf.example)。它只公开以下路径:
|
||
|
||
| 路径 | 用途 | 鉴权与限制 |
|
||
| --- | --- | --- |
|
||
| `/api/v1/apps/<safe-app-id>/completion` | 截图所示的 DashScope-compatible 用户对话 SSE | Go 校验 `xtoken`;请求体 128 KiB;示例限流 5 req/s、burst 20 |
|
||
| `/mcp` | SuperAgent 调用本项目的 MCP | Go 校验独立 `Authorization: Bearer`;请求体 256 KiB;示例限流 20 req/s、burst 40 |
|
||
| `/health` | 可选进程存活检查 | 不访问数据库;如不希望公开可删除该 location |
|
||
| 其他路径 | 不对外提供 | Nginx 固定返回 404 |
|
||
|
||
将示例放入 Nginx 的 `http` 配置范围(例如 `/etc/nginx/conf.d/fire-safety-ymd.conf`)前,必须完成以下替换:
|
||
|
||
1. 把 `location = /api/v1/apps/replace-with-fire-safety-app-id/completion` 中的占位符替换为安全的 app ID。
|
||
2. 该 app ID 必须与 Go 服务的 `FIRE_SAFETY_CHAT_COMPAT_APP_ID` 完全一致;两处不一致会导致请求被拒绝。
|
||
3. 确认证书路径 `/cert/agent.nianxx.com.pem` 和 `/cert/agent.nianxx.com.key` 在公网机器上存在,并由 Nginx 进程可读。
|
||
4. 直接在宿主机运行 Go 时,确认服务只监听 `127.0.0.1:8080`;使用本仓库 Compose 时,容器内监听 `:8080`,但端口映射必须保持为 `127.0.0.1:8080:8080`。两种方式都不要把 8080 或 PostgreSQL 5432 暴露到公网。
|
||
5. 按环境配置 DNS、云防火墙/安全组和 SuperAgent 对 `/mcp` 的来源 IP/TLS 要求;这些部署事实尚未由本项目验证。
|
||
|
||
`limit_req_zone` 必须位于 Nginx `http` context,不能放进 `server` 或 `location`。示例文件假定它被 `conf.d/*.conf` 从 `http {}` 中 include;如果部署系统不是这样 include,应把两条 `limit_req_zone` 指令单独移到 `http {}`,并保留 `server`/`upstream` 在合法上下文。
|
||
|
||
## 3. Go 服务配置
|
||
|
||
在服务器的未提交 Secret 管理方式(例如权限收紧的 `.env`、systemd EnvironmentFile 或容器 Secret)中配置,不要写入 Nginx 文件或 Git:
|
||
|
||
```text
|
||
# 宿主机直接运行时使用 127.0.0.1:8080;Compose 会覆盖为容器内的 :8080,
|
||
# 并只把端口发布到宿主机回环地址。
|
||
FIRE_SAFETY_HTTP_ADDR=127.0.0.1:8080
|
||
|
||
FIRE_SAFETY_SUPERAGENT_ENABLED=true
|
||
FIRE_SAFETY_SUPERAGENT_BASE_URL=https://<superagent-domain>
|
||
FIRE_SAFETY_SUPERAGENT_OPEN_API_KEY=<superagent-open-api-key>
|
||
|
||
FIRE_SAFETY_CHAT_ENABLED=true
|
||
FIRE_SAFETY_CHAT_AUTH_TOKEN=<chat-xtoken-at-least-32-printable-ascii-characters>
|
||
FIRE_SAFETY_CHAT_COMPAT_APP_ID=<same-safe-app-id-as-nginx-location>
|
||
|
||
FIRE_SAFETY_MCP_ENABLED=true
|
||
FIRE_SAFETY_MCP_AUTH_TOKEN=<different-mcp-bearer-at-least-32-printable-ascii-characters>
|
||
FIRE_SAFETY_POSTGIS_ENABLED=true
|
||
FIRE_SAFETY_POSTGIS_DSN=<readonly-postgresql-dsn>
|
||
```
|
||
|
||
当前仍是受控联调静态门禁:
|
||
|
||
- `FIRE_SAFETY_CHAT_AUTH_TOKEN` 由用户请求的 `xtoken` Header 携带,必须与 SuperAgent Open API Key、MCP Token 使用不同值。
|
||
- `FIRE_SAFETY_MCP_AUTH_TOKEN` 只用于 SuperAgent -> Go `/mcp`,不应复用 Chat Token。
|
||
- Chat、SuperAgent、MCP 和 PostGIS 的完整配置校验以 `.env.example` 和对应项目文档为准。
|
||
- 浏览器会看到 `xtoken`;它不能代表最终用户身份、角色、租户或数据授权。公网真实用户入口仍需身份提供方、动态授权、限流、Secret 轮换和持久审计。
|
||
|
||
测试服务器使用 Docker Compose 的完整目录、启动、更新和回滚步骤见 [`docker-test-deployment.md`](docker-test-deployment.md)。
|
||
|
||
## 4. SSE 代理边界
|
||
|
||
兼容对话响应是长连接 SSE。示例对该精确路径设置:
|
||
|
||
- `proxy_http_version 1.1`、清空 `Connection`,保持上游流式连接。
|
||
- `proxy_buffering off`、`proxy_cache off`、`proxy_request_buffering off` 和 `gzip off`,并返回 `X-Accel-Buffering: no`。
|
||
- `proxy_read_timeout`/`proxy_send_timeout` 为 660 秒,覆盖 Go 默认 10 分钟总运行时限并留出少量余量;如提高 Go 的运行时限,必须同步提高 Nginx 超时。
|
||
- `proxy_next_upstream off`,避免 POST 流中断后 Nginx 自动重试而产生重复 SuperAgent Run。
|
||
- 限流拒绝使用 HTTP 429 和稳定 JSON;客户端仍需把网关层非 SSE 响应视为失败,不能等待 `finish_reason=stop`。
|
||
- 透传 `Host`、`X-Forwarded-For`、`X-Forwarded-Proto`、`X-Forwarded-Host`、`X-Forwarded-Port`、`X-Forwarded-Server` 和 `X-Request-ID`。
|
||
- 不设置 `proxy_set_header xtoken ...` 或 `proxy_set_header Authorization ...`;客户端送来的 `xtoken` 原样到 Go,由 Go 做常量时间比较,MCP Bearer 同理。
|
||
|
||
MCP 是普通 JSON 请求,不需要 SSE 的关闭响应缓冲设置;示例使用 30 秒读写超时,仍关闭上游自动重试。
|
||
|
||
## 5. 启用与检查
|
||
|
||
启动 Go 服务前,在服务器的进程环境中加载 Secret。不要把 Secret 直接写进命令行或 shell 历史;可以在受保护的环境文件中加载后,再通过 Header 环境变量展开:
|
||
|
||
直接在宿主机运行时可使用 `go run ./cmd/server`;测试服务器的默认方式是由 Compose 启动镜像,不在宿主机安装或运行 Go 工具链。
|
||
|
||
在 reload 前先检查 Nginx 配置;示例机器没有安装 Nginx 时,本地无法代替目标服务器完成这一检查:
|
||
|
||
```bash
|
||
sudo nginx -t
|
||
sudo nginx -s reload
|
||
```
|
||
|
||
### 首轮兼容对话 SSE
|
||
|
||
以下命令不会把 Token 字面量写入命令历史;`CHAT_XTOKEN` 应由受保护的环境文件或 Secret 管理器注入当前 shell:
|
||
|
||
```bash
|
||
curl -N --fail \
|
||
-H "xtoken: ${CHAT_XTOKEN}" \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'Accept: text/event-stream' \
|
||
--data '{"input":{"prompt":"观水镇附近有哪些水源候选?"},"parameters":{}}' \
|
||
'https://agent.nianxx.com/api/v1/apps/<same-safe-app-id>/completion'
|
||
```
|
||
|
||
客户端应读取 `event: result` 的 SSE 数据,等待 `output.finish_reason` 为 `stop` 后再把最终 `output.text` 视为完成;中途断流不能当作可信答案。首轮返回的 `output.session_id` 是本项目的本地会话 ID,后续是否能继续多轮取决于兼容接口的契约,不能自行把它当作 SuperAgent Provider Session。
|
||
|
||
### 多轮兼容对话 SSE
|
||
|
||
使用上一轮服务返回并确认成功的会话 ID;不要把 SuperAgent 的内部 Session ID 放入客户端请求:
|
||
|
||
```bash
|
||
curl -N --fail \
|
||
-H "xtoken: ${CHAT_XTOKEN}" \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'Accept: text/event-stream' \
|
||
--data '{"input":{"prompt":"再说明这些候选的限制","session_id":"<session-id-from-previous-response>"},"parameters":{}}' \
|
||
'https://agent.nianxx.com/api/v1/apps/<same-safe-app-id>/completion'
|
||
```
|
||
|
||
如果目标客户端不接受 `text/event-stream` 或只需要一次性 JSON,应先以接口 Spec 为准;不要让 Nginx 擅自将 SSE 缓冲成普通 JSON。
|
||
|
||
### MCP 探活/初始化
|
||
|
||
MCP 请求需要独立 Token,具体 JSON-RPC body 以 MCP Spec 为准:
|
||
|
||
```bash
|
||
curl --fail \
|
||
-H "Authorization: Bearer ${MCP_BEARER}" \
|
||
-H 'Content-Type: application/json' \
|
||
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual-probe","version":"1"}}}' \
|
||
'https://agent.nianxx.com/mcp'
|
||
|
||
curl --fail --output /dev/null --write-out 'initialized HTTP %{http_code}\n' \
|
||
-H "Authorization: Bearer ${MCP_BEARER}" \
|
||
-H 'Content-Type: application/json' \
|
||
--data '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
|
||
'https://agent.nianxx.com/mcp'
|
||
|
||
curl --fail \
|
||
-H "Authorization: Bearer ${MCP_BEARER}" \
|
||
-H 'Content-Type: application/json' \
|
||
--data '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
||
'https://agent.nianxx.com/mcp'
|
||
```
|
||
|
||
预期 `initialize` 返回协议版本 `2025-06-18`,initialized notification 返回 HTTP 202,`tools/list` 返回 7 个固定工具。只收到 HTTP 200 但 JSON-RPC 中含 `error` 也属于失败,不能把它当作 MCP 已就绪。
|
||
|
||
## 6. 验收边界与回滚
|
||
|
||
完成 `nginx -t` 和 reload 后,应至少验证:
|
||
|
||
1. `/health` 返回 200(如果保留可选 location)。
|
||
2. 兼容对话首轮在正确 `xtoken` 下返回 SSE,末尾出现 `finish_reason: stop`;错误 Token 返回 401,且 Go 不会创建 SuperAgent Run。
|
||
3. 兼容对话多轮使用返回的本地会话 ID;服务重启或会话失效后按契约返回会话不存在,不把 ID 当成 Provider Session。
|
||
4. `/mcp` 使用独立 Bearer,错误或缺失 Bearer 返回 401;Nginx 没有注入任何 MCP Token。
|
||
5. 未列出的路径(例如 `/api/chat`、`/api/v1/apps/other/completion` 和 `/anything`)返回 404。
|
||
|
||
如果 reload 后发现兼容路由、证书或 SSE 行为异常,先恢复上一个已验证的 Nginx 配置并保留 `nginx -t` 输出;不要通过放开 `location /`、关闭 Go 鉴权或把 Secret 写入 Nginx 来排障。
|
||
|
||
## 7. 未确认事项
|
||
|
||
- 目标公网机器的 Nginx 版本、include 层级、TLS 终止位置和证书续期方式;示例同时监听 80 做 HTTPS 跳转,安全组需按实际策略决定是否允许 80。
|
||
- `agent.nianxx.com` 的 DNS、安全组、反向代理来源 IP 和 SuperAgent 对 MCP 回调的网络策略。
|
||
- 兼容接口在目标 SuperAgent/Profile 中对 `input.session_id`、`parameters` 和 SSE `result` 事件的最终协议;实现以本项目 Spec 和实际联调为准。
|
||
- 生产最终用户认证、动态授权、会话共享/持久化、主动取消、配额、审计和 Secret 轮换。
|