Files
fire-safety-ymd/docs/project/operations/nginx-public-entry.md
2026-09-06 00:25:25 +08:00

16 KiB
Raw Blame History

Nginx 公网入口(受控联调示例)

项 内容
目标 让 agent.nianxx.com 通过 HTTPS 访问本项目的可选测试页面、兼容对话接口和 MCP
状态 示例配置;公网机器、证书、网络白名单和真实鉴权仍待联调
上游 仅反代本机 127.0.0.1:16587;容器内部仍监听 8080

1. 与旧配置的变化

旧配置把整个 / 转发到 DashScope,并在 Nginx 中比较 xtoken、注入 DashScope API Key。新边界是:

公网 HTTPS
  -> Nginx(TLS、精确路径、限流、SSE 传输设置)
  -> 127.0.0.1:16587(Docker 发布端口)
  -> 容器内 :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。它只公开以下路径:

路径 用途 鉴权与限制
/api/v1/apps/<safe-app-id>/completion 截图所示的 DashScope-compatible 用户对话 SSE Go 校验 xtoken;请求体 128 KiB;示例限流 5 req/s、burst 20
/chat、/chat/ 可选的受控测试对话页面 /chat 由 Go 规范化重定向到 /chat/(308);页面 GET 不要求 Token;Go 由 FIRE_SAFETY_CHAT_PAGE_ENABLED 决定返回 HTML 或 404;示例限流 5 req/s、burst 20
/chat/app.css、/chat/app.js 测试页面静态资源 页面开关关闭时由 Go 返回 404;开启时返回对应资源;示例限流 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 时,应选择未占用的回环端口并同步修改 Nginx upstream;使用本仓库 Compose 时,容器内监听 :8080,端口映射保持为 127.0.0.1:16587:8080。不要把 16587、容器 8080 或 PostgreSQL 5432 暴露到公网。
  5. 按环境配置 DNS、云防火墙/安全组和 SuperAgent 对 /mcp 的来源 IP/TLS 要求;这些部署事实尚未由本项目验证。

示例中的 location = /chat、location = /chat/、location = /chat/app.css 和 location = /chat/app.js 是页面及其资源的精确入口,均反代到同一个 127.0.0.1:16587 上游。Nginx 不判断页面开关,也不提供备用 HTML、CSS 或 JavaScript:

  • FIRE_SAFETY_CHAT_PAGE_ENABLED=true 且依赖完整时,Go 返回页面 HTML;
  • 页面请求 /chat 时,Go 返回 308 并规范化到 /chat/;随后 /chat/ 返回页面 HTML;
  • 页面请求 CSS/JavaScript 资源时,Go 返回资源内容;
  • 开关为 false 或页面依赖不满足时,Go 对页面和资源路径返回 404;
  • 不要把这些 location 改成旧配置的 location /,也不要在 Nginx 中注入 xtoken。

页面默认关闭。测试开启时,Go 进程配置的 FIRE_SAFETY_CHAT_ALLOWED_ORIGINS 必须包含精确 Origin https://agent.nianxx.com;该值是浏览器从页面同源发出 completion 请求时的来源。不要 用 *,也不要把 Token 写入 Nginx。

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:

# Compose 会覆盖为容器内的 :8080,并发布到宿主机回环地址 16587。
# 若直接在宿主机运行,则使用 127.0.0.1:16587 并保持 Nginx upstream 一致。
FIRE_SAFETY_HTTP_ADDR=127.0.0.1:16587

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>
# Keep false for new environments. Only set true in a controlled migration
# when an already-issued legacy Chat credential is shorter than 32 characters.
FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN=false
FIRE_SAFETY_CHAT_COMPAT_APP_ID=<same-safe-app-id-as-nginx-location>
# Optional public test page; keep false unless this test surface is needed.
FIRE_SAFETY_CHAT_PAGE_ENABLED=false
# If the page is enabled, this exact same-origin value is required.
FIRE_SAFETY_CHAT_ALLOWED_ORIGINS=https://agent.nianxx.com

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 使用不同值。
  • 默认要求 Chat Token 至少 32 个可打印 ASCII 字符;若已交付的旧客户端短凭证无法立即更换,只能在 Go 服务环境中显式设置 FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN=true 做受控测试/迁移。无论开关如何,Token 都必须非空、不超过 4096 字节,并且只含 ASCII 0x21-0x7e(无空格、控制字符或 Unicode);轮换完成后恢复 false,新环境不得开启。该开关同时适用于原生 Bearer 和兼容 xtoken,不放宽 MCP Token 的至少 32 字符门禁。
  • FIRE_SAFETY_MCP_AUTH_TOKEN 只用于 SuperAgent -> Go /mcp,不应复用 Chat Token。
  • Chat、SuperAgent、MCP 和 PostGIS 的完整配置校验以 .env.example 和对应项目文档为准。
  • 浏览器会看到 xtoken;它不能代表最终用户身份、角色、租户或数据授权。公网真实用户入口仍需身份提供方、动态授权、限流、Secret 轮换和持久审计。
  • FIRE_SAFETY_CHAT_PAGE_ENABLED 默认必须为 false。开启页面只适用于受控测试;页面 GET 本身 不需要 Token,用户在页面输入的 xtoken 只在当前页面内存中使用,不写入 HTML、Cookie、URL、 localStorage 或 sessionStorage。
  • 页面与兼容接口同源时,FIRE_SAFETY_CHAT_ALLOWED_ORIGINS 必须包含精确的 https://agent.nianxx.com。这不是把页面变成生产认证;Token 仍为静态测试凭证,必须由用户 手动输入并按测试范围管理。
  • Nginx 不读取、比较或存储 Chat Token,也不需要为 legacy 开关增加配置;请求 Header 原样转发给 Go 校验。开关启用时 Go 仅记录不含 Secret 的安全 warning,便于迁移完成后清理。

测试服务器使用 Docker Compose 的完整目录、启动、更新和回滚步骤见 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 时,本地无法代替目标服务器完成这一检查:

sudo nginx -t
sudo nginx -s reload

首轮兼容对话 SSE

以下命令不会把 Token 字面量写入命令历史;CHAT_XTOKEN 应由受保护的环境文件或 Secret 管理器注入当前 shell:

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 放入客户端请求:

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。

受控测试页面

页面由 Go 在开关开启时提供,Nginx 只做四个精确路径的反代(页面、规范化入口和两个资源):

curl -i https://agent.nianxx.com/chat
curl -i https://agent.nianxx.com/chat/

预期行为:

  • FIRE_SAFETY_CHAT_PAGE_ENABLED=false:/chat 和 /chat/ 均直接返回 HTTP 404;
  • FIRE_SAFETY_CHAT_PAGE_ENABLED=true 且 Chat 兼容配置完整:/chat 返回 HTTP 308, /chat/ 返回 HTTP 200,Content-Type 为 text/html;
  • 开启时页面加载的 /chat/app.css 和 /chat/app.js 也应返回 HTTP 200;关闭时均为 404。

需要跟随 /chat 的规范化重定向时使用:

curl -iL https://agent.nianxx.com/chat

浏览器打开 https://agent.nianxx.com/chat/ 后,手动输入受控测试 xtoken 和不含敏感信息的 问题。页面使用 fetch 向同源的 /api/v1/apps/<same-safe-app-id>/completion 发起 POST,Header 为 xtoken、 Content-Type: application/json、Accept: text/event-stream,请求体仍是:

{"input":{"prompt":"观水镇附近有哪些地点候选?"},"parameters":{}}

页面只在内存中保存最终成功的 output.session_id,下一轮把它放入 input.session_id;刷新或 关闭页面后不恢复。只有 finish_reason=stop 才显示为成功,初始 finish_reason=null、HTTP 错误、event: error 或断流不能当作完整答案。页面不会把 xtoken 写入 HTML、浏览器存储、Cookie、 URL、Nginx 或日志。

浏览器 Network 面板应能确认:请求为同源 POST、Origin 为 https://agent.nianxx.com、使用正确 的公开 App ID、没有跨域失败,并且第二次请求包含上一轮返回的 session_id。如果出现 403,优先 检查 FIRE_SAFETY_CHAT_ALLOWED_ORIGINS 是否包含精确 Origin;如果出现 404,检查页面开关和 容器是否已 recreate;如果出现 401,重新输入正确的测试 xtoken。

MCP 探活/初始化

MCP 请求需要独立 Token,具体 JSON-RPC body 以 MCP Spec 为准:

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. 页面开关关闭时 /chat 和 /chat/ 均直接返回 404;开启且依赖完整时 /chat 返回 308、/chat/ 返回 200 HTML,且两个资源路径返回 200。
  3. 兼容对话首轮在正确 xtoken 下返回 SSE,末尾出现 finish_reason: stop;错误 Token 返回 401,且 Go 不会创建 SuperAgent Run。
  4. 兼容对话多轮使用返回的本地会话 ID;服务重启或会话失效后按契约返回会话不存在,不把 ID 当成 Provider Session。
  5. /mcp 使用独立 Bearer,错误或缺失 Bearer 返回 401;Nginx 没有注入任何 MCP Token。
  6. 未列出的路径(例如 /api/chat、/api/v1/apps/other/completion 和 /anything)返回 404。

如果 reload 后发现兼容路由、证书或 SSE 行为异常,先恢复上一个已验证的 Nginx 配置并保留 nginx -t 输出;不要通过放开 location /、关闭 Go 鉴权或把 Secret 写入 Nginx 来排障。

如只需紧急关闭测试页面,不必改动 Nginx:将 Go 环境中的 FIRE_SAFETY_CHAT_PAGE_ENABLED 改为 false,重新创建容器后确认 /chat、/chat/ 和资源 路径均直接返回 404。这样不会自动关闭兼容 completion;如果也要关闭对话 API,再按 Chat 配置和对应回滚流程 处理。页面开关和 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 轮换。