Files
Genarrative/deploy/nginx/README.md
T
kdletters 55df19da9f 记录 Pingora dev 阶段验收
记录 dev 持久 shadow service 真实链路验收结果

记录 dev Nginx 前缀 canary 验收和恢复状态

补充 canary 前必须先完成持久 shadow 验收的口径
2026-06-17 21:48:26 +08:00

94 lines
24 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.
# Genarrative Nginx compression policy
本配置片段由 `scripts/jenkins-server-provision.sh` 在安装 Nginx 站点配置时展开。
## 请求体大小
- 生产、开发服和容器模板都在通用 `location ~ ^/api(?:/|$)` 内设置 `client_max_body_size 64m`。
- 该值只用于让携带参考图 Data URL 的创作接口抵达 `api-server`;不要把它当作业务上传上限。Rust 路由仍通过 `DefaultBodyLimit` 和解码后字节校验限制具体接口,例如拼图参考图路由只放宽到 12 MiB 请求体,图片字节继续按业务规则拒绝。
- 若线上看到 `413 Request Entity Too Large`,并且 access log 里 `request_time=0.000 upstream_status=-`,通常是 Nginx 没有加载该模板或未 reload;先执行 `nginx -T | grep client_max_body_size` 和 `nginx -t` 再检查 `api-server`。
## gzip
- `deploy/nginx/genarrative.conf` 与 `deploy/nginx/genarrative-dev-http.conf` 默认开启 gzip。
- 覆盖 `application/json`,用于降低 `/api/runtime/*/gallery` 这类 JSON 列表接口的公网带宽占用。
- 当前推荐等级为 `gzip_comp_level 5`,兼顾 2C/2G 服务器 CPU 与压缩收益。
- Pingora 影子网关已通过 `GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=true`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5` 与 `GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 承接 gzip parity;网关会把压缩算法收敛到 gzip allowlist,并按 `gzip_min_length 1024` 避免压缩小响应,同时沿用 Nginx `gzip_types` 边界不压缩图片资源。改动后必须运行 `npm run check:pingora-gateway-smoke` 验证小响应不压缩、图片资源不压缩,以及 `Accept-Encoding: gzip` 和 `Accept-Encoding: br, gzip` 的大响应都返回 gzip。
## Brotli
- Brotli 只在目标服务器 Nginx 接受 brotli 指令时开启。
- Pingora 当前正式化口径固定为 gzip-only,不承接 Brotli;做 Nginx -> Pingora canary 时,Brotli 仍由 Nginx 或更前置代理决定是否启用。直连 Pingora 时,不把 Brotli parity 作为切换门禁,避免使用 Pingora 0.8.1 里尚未通过可解压端到端验证的 Brotli compressor 路径。
- Ubuntu / apt 系统的 `Genarrative-Server-Provision` 会安装 `libnginx-mod-http-brotli-filter` 与 `libnginx-mod-http-brotli-static`;非 apt 系统暂不自动安装,仍按下面的能力探测结果决定是否启用。
- Provision 脚本通过临时配置执行 `nginx -t` 做能力探测;探测配置会先 `include /etc/nginx/modules-enabled/*.conf`,避免 Ubuntu 动态模块已安装但测试配置未加载模块导致误判。可用时把模板中的 `# __GENARRATIVE_BROTLI_DIRECTIVES__` 替换为 brotli 指令,不可用时保留注释说明。
- 不要直接在静态模板里无条件写 `brotli on;`,否则没有 brotli 模块的服务器会 `nginx -t` 失败并回滚。
- 不要用 `nginx -V | grep brotli` 判断 brotli 是否可用;Ubuntu apt 安装的 brotli 是动态模块,不会出现在普通编译参数里。应检查包、`/etc/nginx/modules-enabled/` 的 `load_module` 配置,或用包含 `include /etc/nginx/modules-enabled/*.conf` 的临时配置执行 `nginx -t`。
## Pingora upstream timeouts
- Pingora 影子网关已显式配置上游连接 / 读 / 写 timeout,避免正式 canary 时依赖 Pingora 默认值。
- 默认连接超时为 `3000ms`;没有 Nginx 显式长超时的代理路由读取超时为 `60s`。
- 通用 `/api`、公开列表 / 详情和 SpacetimeDB subscribe 读取超时为 `3600s`,写上游超时为 `3600s`,对齐当前 Nginx `proxy_read_timeout` / `proxy_send_timeout` 口径。
- 超时会返回 JSON `504 GATEWAY_UPSTREAM_TIMEOUT`,由 `npm run check:pingora-gateway-smoke` 覆盖。
## Pingora static cache policy
- Pingora 直连静态响应会显式写入 `Cache-Control`,避免公网入口切换后 HTML 入口页或 Vite 指纹资源依赖框架默认头。
- 默认 `GENARRATIVE_PINGORA_GATEWAY_HTML_CACHE_CONTROL=no-cache`,覆盖 `index.html`、`admin/index.html`、目录 index 和 SPA fallback。
- 默认 `GENARRATIVE_PINGORA_GATEWAY_ASSET_CACHE_CONTROL=public, max-age=31536000, immutable`,只覆盖 `/assets/*` 与 `/admin/assets/*` 中带 Vite 指纹文件名的资源;非指纹静态和 ACME challenge 默认 `GENARRATIVE_PINGORA_GATEWAY_STATIC_CACHE_CONTROL=no-cache`。
- Pingora 静态响应同时写入弱 `ETag`、`Last-Modified` 与 `Accept-Ranges: bytes`,并对 `If-None-Match` / `If-Modified-Since` 返回 `304`;这层只负责浏览器协商缓存,不改变上面的 `Cache-Control` 分档。
- Pingora 静态文件支持单段 `Range: bytes=`:合法范围返回 `206` 与 `Content-Range`,越界范围返回 `416` 与 `Content-Range: bytes */<len>`,`HEAD + Range` 只返回头。多段 range 暂按完整文件返回,不启用 multipart 响应。`If-Range` 日期匹配时继续返回局部内容,日期旧于文件或弱 ETag 校验器时回完整 `200`。`206` / `304` / `416` 均不做 gzip 压缩,避免局部内容语义和 `Content-Range` 漂移。
- Pingora 静态路由只允许 `GET` / `HEAD`。非读取方法命中静态候选时返回 `405` 与 `Allow: GET, HEAD`;缺失文件仍返回 `404`,避免错误客户端把静态入口当作可写接口。
- `npm run check:pingora-gateway-smoke` 会用固定 `X-Request-Id` 对账静态 `304` / `405` / `206` / `416` 的 Pingora access log 行,并同时校验 method / path / status;该 smoke 还会让 direct live 自动发现首页里的普通静态资源和 Vite 指纹资源,确认指纹资源 GET / HEAD / `If-None-Match` 304 / `Range: bytes=0-0` 以及 `public, max-age=31536000, immutable` 缓存头,确保本地静态响应状态和指纹 chunk 长缓存都可进入直连切换证据链。direct live JSON 只给静态 GET / HEAD / 304 / Range 写入白名单 `headers`,包括 `cache-control`、`etag`、`last-modified`、`accept-ranges`、`content-range`、`content-length` 和 `content-encoding`,便于证据包归档后复盘静态缓存与 Range 响应头。
## Pingora TLS / redirect
- Pingora 网关默认仍只监听本机 shadow 端口,不绑定公网 `80/443`。
- 需要评估直连入口时,显式配置 `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN`、`GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE` 和 `GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE`;网关只读取现有证书文件,不负责 Certbot / ACME 自动续期。
- 需要 HTTP 到 HTTPS 跳转时,再显式配置 `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN`;该入口除 `/.well-known/acme-challenge/*` 外统一 301 到 HTTPS,ACME challenge 仍从 `GENARRATIVE_PINGORA_GATEWAY_ACME_ROOT` 精确读取。
- API release 必须把 `scripts/check-pingora-release-readiness.mjs`、`scripts/check-pingora-canary-live.mjs`、`scripts/check-pingora-canary-access-log-parity.mjs`、`scripts/ops/pingora-current-release-audit.mjs`、`scripts/ops/pingora-cutover-status-snapshot.mjs`、`scripts/ops/pingora-cutover-evidence-bundle.mjs`、`scripts/check-pingora-direct-preflight.mjs`、`scripts/check-pingora-direct-live.mjs`、`scripts/check-production-health-patrol-env.mjs`、`scripts/deploy/pingora-health-patrol-env-switch.mjs`、`deploy/systemd/`、`deploy/env/` 和 `deploy/pingora/` 一起复制到 `/opt/genarrative/current`;current release 自审、直连启用、health patrol env 切换、canary live、access log 对账、cutover 状态快照和证据包脚本都从 current release 推导依赖路径,切换窗口不得依赖 Jenkins 工作区或目标机源码 checkout。
- 默认 systemd service 以 `genarrative` 非 root 用户运行,不具备绑定 `80/443` 的能力。Server-Provision 只安装 `/etc/genarrative/pingora/genarrative-pingora-gateway-direct-entry.conf` 作为人工启用模板;切直连低端口时必须先确认 `genarrative` 可读证书文件、Nginx 已释放 `80/443`,再执行 `/opt/genarrative/current/scripts/deploy/pingora-direct-enable.sh --apply --preflight-env-file /etc/genarrative/pingora-gateway.env --preflight-check-cert-readable --preflight-check-service-env-file --preflight-check-service-user-cert-readable --preflight-check-service-binary-executable --preflight-check-ports-free --direct-https-base-url https://127.0.0.1 --direct-http-base-url http://127.0.0.1 --direct-host <域名> --direct-redirect-host <域名或host:port> --direct-spacetime-database <库名> --direct-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;脚本会先执行随包 current release 自审,确认 `pingora-gateway` 可执行且 systemd `ExecStart` 指向 current release,失败时不会安装 drop-in。自审通过后才安装 drop-in、`systemctl daemon-reload` 并重启 Pingora;脚本会用 `systemctl cat` 确认 `CAP_NET_BIND_SERVICE` drop-in 已进入 systemd 最终配置,用 `systemctl show ... ExecStart` 确认最终 service 指向随包主 service 模板中的 current release `pingora-gateway`,用 `systemctl is-active` 确认 Pingora active,并执行 direct live smoke,强制覆盖 HTTPS / HTTP redirect / ACME / WSS 101 和 Pingora access log request_id 落盘。
- `npm run check:pingora-gateway-smoke` 会用临时自签证书覆盖 TLS 直连、HTTP redirect 和 WSS subscribe 行为;正式公网切换前仍必须先走 Nginx canary 和 release readiness 门禁。
- 目标机直连入口启用后先运行 `npm run check:pingora-direct-preflight -- --env-file /etc/genarrative/pingora-gateway.env --require-live-env --systemd-cat --check-cert-readable --check-service-env-file --check-service-user-cert-readable --check-service-binary-executable --check-ports-free`,再运行 `npm run check:pingora-direct-live`,检查 HTTPS 根路径、HTTP/2 ALPN、代表性 API、SpacetimeDB identity、WSS subscribe 握手、generated 拒绝、公开 `/healthz` 拒绝、可选 shadow probe,以及 HTTP 301 / ACME challenge;真实 SpacetimeDB 对 `GET /v1/identity` 返回 `405 Method Not Allowed` 属于可接受语义,direct live 只把它作为路径转发代表,不要求该 GET 创建 identity;release readiness 可用 `--require-direct --direct-https-base-url https://127.0.0.1 --direct-http-base-url http://127.0.0.1 --direct-host <域名> --direct-redirect-host <域名或host:port> --direct-spacetime-database <库名> --direct-pingora-access-log /var/log/genarrative/pingora-gateway.access.log --direct-health-patrol-env-file /etc/genarrative/health-patrol.env --direct-preflight-env-file /etc/genarrative/pingora-gateway.env --direct-preflight-systemd --direct-preflight-check-cert-readable --direct-preflight-check-service-env-file --direct-preflight-check-service-user-cert-readable --direct-preflight-check-service-binary-executable --direct-preflight-check-ports-free` 强制纳入同一条门禁,漏掉 direct HTTP base URL、正式域名 Host/SNI、redirect Location host、目标 SpacetimeDB 库、Pingora access log request_id 落盘、health patrol env direct 模式复核、systemd drop-in 生效检查、service EnvironmentFile 一致性检查、当前用户 / systemd 服务用户证书可读检查、service 二进制可执行检查或 80/443 端口释放检查都会直接失败。
- direct live 在 HTTPS 根路径返回 `200` 且 HTML 中发现 `/assets/` 或 `/admin/assets/` 引用时,会自动请求该静态资源,确认 `Cache-Control`、`ETag`、`Last-Modified`、`Accept-Ranges: bytes`、`HEAD` 头响应、`If-None-Match` / `If-Modified-Since` 的 `304` 响应和 `Range: bytes=0-0` 的 `206 + Content-Range` 响应,并纳入 Pingora access log method/path/status 对账;若首页引用 Vite 指纹资源,还会额外确认 `Cache-Control: public, max-age=31536000, immutable` 以及指纹资源 GET / HEAD / 304 / Range access log 证据。维护模式、非 HTML 或发布包首页没有资产引用时该项标记为 skipped,不阻断维护窗口。
- 直连启用前先 dry-run `/opt/genarrative/current/scripts/deploy/pingora-direct-enable.sh --no-status`;验证失败时执行 `/opt/genarrative/current/scripts/deploy/pingora-direct-rollback.sh --apply --reload-nginx --nginx-smoke-url http://127.0.0.1/healthz --nginx-smoke-host <域名> --nginx-smoke-expect-body '"ok":true'`,脚本会先运行 `nginx -t`,通过后移除 direct-entry drop-in、重启 Pingora,用 `systemctl cat` 确认 `CAP_NET_BIND_SERVICE` 已从 systemd 最终配置中移除,用 `systemctl show ... ExecStart` 确认最终 service 仍指向随包主 service 模板中的 current release `pingora-gateway`,并 reload Nginx、确认 Nginx service 仍为 `active`,最后用 smoke URL 证明 Nginx 入口真实可访问;传入 `--nginx-smoke-expect-body` 时还会要求响应体包含该片段。仓库工作区可用 `npm run deploy:pingora-direct-enable -- --apply --preflight-env-file /etc/genarrative/pingora-gateway.env --preflight-check-cert-readable --preflight-check-service-env-file --preflight-check-service-user-cert-readable --preflight-check-service-binary-executable --preflight-check-ports-free --direct-https-base-url https://127.0.0.1 --direct-http-base-url http://127.0.0.1 --direct-host <域名> --direct-redirect-host <域名或host:port> --direct-spacetime-database <库名> --direct-pingora-access-log /var/log/genarrative/pingora-gateway.access.log` 和 `npm run deploy:pingora-direct-rollback -- --apply --reload-nginx --nginx-smoke-url http://127.0.0.1/healthz --nginx-smoke-host <域名> --nginx-smoke-expect-body '"ok":true'`。两个脚本默认 dry-run;启用脚本 `--apply` 必须先通过 current release 自审,并带 env、证书可读、service EnvironmentFile 一致性、服务用户证书可读、service 二进制可执行、端口释放预检、direct live 参数和 Pingora access log 参数,回退脚本 `--apply` 必须带 `--reload-nginx` 和 `--nginx-smoke-url`;如果 health patrol env 已预先切回 Nginx,也可追加 `--health-patrol-env-file /etc/genarrative/health-patrol.env --health-patrol-expected-public-base-url <切换前Nginx巡检入口> --health-patrol-require-empty-public-host` 让回退脚本在 Nginx smoke 后复核 `gateway mode=nginx` 且 public base URL / Host 已恢复,若切换前 Nginx 巡检本来需要 Host 覆盖,则把最后一项换成 `--health-patrol-expected-public-host <切换前Host>`;如果还要同时证明 shadow 高端口仍健康,可追加 `--pingora-shadow-probe-url http://127.0.0.1:18081/__genarrative_pingora/healthz --pingora-shadow-probe-token <token>`,脚本会隐藏 token 并要求响应包含 `gateway=pingora-shadow`。本机 `npm run check:pingora-direct-enable` / `npm run check:pingora-direct-rollback` 会验证 dry-run 不会安装或删除临时 drop-in、current release 自审失败时启用脚本不会安装 drop-in,并展示启用 / 回退后的 systemd 最终配置核验、ExecStart 指向核验、Pingora active 核验、direct live smoke、Nginx 语法检查、reload 状态核验、Nginx smoke 及响应体片段核验、可选 health patrol env 复核和可选 shadow probe 复核。
- `npm run check:pingora-current-release-audit` 会烟测 current release 自审脚本;正式直连 runbook 会先执行 `/opt/genarrative/current/scripts/ops/pingora-current-release-audit.mjs --release-root /opt/genarrative/current --require-pingora-gateway --systemd-show`,只读确认发布包自包含、`api-server.sha256` / `pingora-gateway.sha256` 匹配、`release-manifest.api-server.json` 已登记 `pingora-gateway`、`pingora-gateway` 可执行且 systemd `ExecStart` 指向 current release。
- `npm run check:pingora-cutover-status-snapshot` 会烟测状态快照脚本,`npm run check:pingora-cutover-evidence-bundle` 会烟测证据包脚本;正式切换窗口与 release readiness 使用 `/opt/genarrative/current/scripts/ops/pingora-cutover-evidence-bundle.mjs` 归档切换前 / 启用后 / 回退后三个阶段的 `manifest.json`、`snapshot.json`、stdout / stderr 和命令记录。直连 runbook 的三个证据包阶段都会向状态快照透传 `--require-pingora-gateway`,在 `checks.current-release-audit` 中保存 Pingora 二进制、sha256、release manifest 和 systemd `ExecStart` 自审结果;启用后证据包还会通过 `--run-direct-live` 保存 `direct-live.json`、direct live stdout / stderr、命令记录、Pingora access log `request_id` 反查结果,以及静态响应头白名单证据。`manifest.summary.directLiveAccessLog` 会提升 checked / matched / missing / mismatch 对账摘要,`manifest.summary.directLiveStaticHeaders` 会提升普通静态和 Vite 指纹静态的缓存头、校验头、Range `Content-Range` 和 304 状态摘要,便于值班人员不打开 `direct-live.json` 也能先判断证据是否完整;如果 direct live 已输出静态资产结果但摘要缺少缓存头、校验头、Range `206 + Content-Range`、ETag 304 或 Last-Modified 304 证据,证据包会记为 `CRITICAL`。最终总审计还会带 `--require-phase-direct-live-access-log post-enable --require-phase-direct-live-static-headers post-enable`,防止缺少 access log 对账摘要或静态头摘要的旧启用后证据包混入正式归档。快照脚本只读采集状态,证据包脚本只写指定证据目录,不写 `/etc`、不 reload systemd,也不修改 Nginx 或 Pingora。
- 最终证据根目录总审计 JSON 先看 `summary.status`、`summary.failedItems[]` 和 `summary.directLiveEvidence[]`:失败时这里会直接指出阶段 / 命令 / 根目录问题,以及 `post-enable` access log 或静态头摘要的短 reason;`phases[]`、`commands[]` 和 `timeline` 继续作为完整机器归档。
- 最终证据根目录总审计必须同时带 `--require-command-executable enable-apply:pingora-direct-enable-apply:/opt/genarrative/current/scripts/deploy/pingora-direct-enable.sh` 和 `--require-command-executable rollback-apply:pingora-direct-rollback-apply:/opt/genarrative/current/scripts/deploy/pingora-direct-rollback.sh`,避免只证明有命令证据,却没有证明真实执行的是 current release 随包 enable / rollback 脚本。
- `npm run check:production-api-release` 会动态生成 API release,并检查发布包 README 与随包 `scripts/check-pingora-release-readiness.mjs --dry-run-cutover` 输出的最终总审计步骤都保留上述两条 `--require-command-executable` 要求,避免源码文档正确但发布包操作口径漂移。
- 直连接管后用 `node -- /opt/genarrative/current/scripts/deploy/pingora-health-patrol-env-switch.mjs --apply --env-file /etc/genarrative/health-patrol.env --gateway-mode pingora-direct --public-base-url <直连HTTPS入口> --public-host <域名>` 切换 health patrol env;该脚本只改 gateway mode、public base URL 和 public Host,并先用临时目标 env 调用随包 `check-production-health-patrol-env.mjs` 复核,复核通过后才原子替换真实 env,复核失败不会落盘。回退到 Nginx 前用同一脚本改回 `--gateway-mode nginx --public-base-url <切换前Nginx巡检入口>`,若切换前 Nginx 巡检不需要 Host 覆盖则传 `--clear-public-host`,需要 Host 覆盖则传 `--public-host <切换前Host>`;随后 rollback 脚本和独立 env 复核会再次阻断 public base URL / Host 漂移。`plan:pingora-direct-cutover` 会在 JSON runbook 中列出 direct / nginx 两个方向的 env 切换脚本和 env 复核步骤,并先输出 Host 与回退巡检入口确认;正式切换 runbook 会拒绝 `--direct-redirect-host` 或 `--rollback-nginx-smoke-host` 与 `--direct-host` 使用不同 hostname,且必须提供 `--rollback-health-patrol-public-base-url <切换前Nginx巡检入口>`,确认步骤会展示回退后要恢复的 public base URL / Host,避免回退计划把现场巡检入口覆盖成仓库默认值。若切换计划希望回退后同时证明 Pingora shadow 高端口仍健康,可给 runbook 追加 `--rollback-pingora-shadow-probe-url http://127.0.0.1:18081/__genarrative_pingora/healthz --rollback-pingora-shadow-probe-token <token>`;JSON 输出会隐藏 token 原文,并把对应参数传给 rollback dry-run / apply 步骤。
## Pingora canary
- `deploy/nginx/snippets/genarrative-pingora-canary.conf` 是默认不启用的人工前缀 canary 入口,只在需要验证 Nginx -> Pingora handoff 时临时 include。它使用 `/__genarrative_pingora_canary/` 前缀改写后转发到 `127.0.0.1:18081`,并返回 `X-Genarrative-Nginx-Handoff: pingora-canary`。
- `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 是前缀 canary 之后、direct 直连之前的真实路径 canary。它只能在 Nginx `http` 上下文人工 include,默认监听 `127.0.0.1:18083`,覆盖 `/api/creation-entry/config`、`/v1/identity`、`/v1/database/<db>/subscribe`、`/assets/app.js` 和拒绝入口,并写入独立 `/var/log/nginx/genarrative-pingora-realpath-canary.access.log`;不要把它 include 到生产 `443` server 内作为 location 覆盖。
- 启用 Nginx canary 前,目标机必须已经有持久 `genarrative-pingora-gateway.service` 运行在 `127.0.0.1:18081`,且带真实 `api-server`、SpacetimeDB、静态目录和 `/var/log/genarrative/pingora-gateway.access.log` 完成本机 shadow 验收;不要从一次性 `/tmp` 网关进程直接切到 Nginx handoff。
- 启用前必须运行 `npm run check:nginx-pingora-canary`;目标 agent 有 Nginx 时运行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,同时做两个 snippet 的静态护栏和真实 `nginx -t`。本机或 CI 可运行 `npm run check:pingora-canary-docker`,用 Docker Nginx、真实 Pingora 和 mock 上游复现前缀 canary 与真实路径 canary 两条 handoff 链路。
- 前缀 canary reload 后运行 `GENARRATIVE_PINGORA_CANARY_BASE_URL=http://127.0.0.1 GENARRATIVE_PINGORA_CANARY_HOST=<域名> npm run check:pingora-canary-live`,再用 `scripts/check-pingora-canary-access-log-parity.mjs` 对账 `/var/log/nginx/genarrative.access.log` 与 `/var/log/genarrative/pingora-gateway.access.log`。
- 真实路径 canary reload 后运行 `node scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再运行 `node scripts/check-pingora-canary-access-log-parity.mjs --realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/creation-entry/config --path /v1/identity --path /assets/app.js`。真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。
- 正式切换前在源码 checkout / CI / 构建环境跑 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;启用真实路径 canary 时追加 `--require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。
- 目标机 `/opt/genarrative/current` 只能跑随包 runtime-only 门禁:`/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;验证真实路径 canary 或 Pingora 直连入口时分别追加 `--require-realpath-live ...` 或 `--require-direct ...` 参数。生成正式直连切换 runbook 时,`--direct-redirect-host` 和回退 smoke Host 必须与 `--direct-host` 同 hostname,只允许端口不同。
## 验证
```bash
dpkg -l 'libnginx-mod-http-brotli-*'
ls -l /etc/nginx/modules-enabled/*brotli*
nginx -T 2>/dev/null | grep -Ei 'brotli|load_module'
curl -sSI -H 'Accept-Encoding: gzip' \
http://<host>/api/runtime/puzzle/gallery \
| grep -iE 'content-encoding|vary|content-type|content-length'
curl -sSI -H 'Accept-Encoding: br' \
http://<host>/api/runtime/puzzle/gallery \
| grep -iE 'content-encoding|vary|content-type|content-length'
```
预期:
- gzip 可用时返回 `Content-Encoding: gzip`。
- br 可用时返回 `Content-Encoding: br`。
- 响应头应包含 `Vary: Accept-Encoding`。