Files
fire-safety-ymd/docs/project/operations/nginx-public-entry.md
T
2026-09-05 15:46:37 +08:00

176 lines
10 KiB
Markdown
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.
# 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 轮换。