游戏发行入口改为平台同源路径
Project CI / AI game creator shell Rust crates (push) Successful in 1m26s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m11s
Project CI / Backend tests (push) Successful in 5m12s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m26s
Project CI / Native shell tests (push) Successful in 6m31s
Project CI / Frontend tests (push) Successful in 2m20s
Project CI / Repository checks (push) Successful in 2m25s
Project CI / AI game creator shell web tests (push) Successful in 1m21s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Successful in 1m26s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m11s
Project CI / Backend tests (push) Successful in 5m12s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m26s
Project CI / Native shell tests (push) Successful in 6m31s
Project CI / Frontend tests (push) Successful in 2m20s
Project CI / Repository checks (push) Successful in 2m25s
Project CI / AI game creator shell web tests (push) Successful in 1m21s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
- 审核通过时由 api-server 按 gameId 派生 /games/{gameId}/ 相对路径写入公开投影,删除 AppConfig 的发行入口模板字段与读取逻辑
- 删除 deploy/env 两份示例中的 GENARRATIVE_GAME_DISTRIBUTION_RELEASE_ENTRY_TEMPLATE
- 三份 nginx 模板内联同源发行入口 location,把 /games/<gameId>/ 与子资源转发到发行网关并在边缘清空 Cookie
- SPA allowlist 补齐 components、design-system、games、games/detail、games/mine、games/play、games/publish
- 前端 normalizeGameEntryUrl 支持相对路径与同源发行路径,按当前 origin 解析并补尾斜杠,继续兼容历史绝对 URL
- 删除退役的独立来源模板 deploy/nginx/genarrative-release-origin.conf、门禁脚本 scripts/check-release-origin-config.mjs 与其 npm 脚本
- 游戏分发 e2e 脚本改为在公开投影上断言 entryUrl 等于 /games/{gameId}/
- 同步平台主规范、运维主规范、nginx README 与共享决策记录
This commit is contained in:
@@ -72,6 +72,5 @@ GENARRATIVE_LLM_MODEL=gpt-5.4-mini
|
||||
WECHAT_MINIPROGRAM_MESSAGE_TOKEN=
|
||||
WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY=
|
||||
|
||||
# 游戏发行入口模板:审核通过时按 {gameId} 占位符派生每游戏独立来源地址,例如
|
||||
# https://{gameId}.games.example.com/。模板必须含 {gameId},生产未配置时审核通过直接失败。
|
||||
GENARRATIVE_GAME_DISTRIBUTION_RELEASE_ENTRY_TEMPLATE=
|
||||
# 游戏发行入口固定为平台同源路径 /games/{gameId}/:审核通过时由 api-server 自己派生,
|
||||
# 不需要部署侧配置发行域名或通配证书。
|
||||
|
||||
@@ -136,12 +136,28 @@ http {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# 平台同源路径发行入口:/games/<gameId>/ 与 /games/<gameId>/<asset> 映射到
|
||||
# api-server 发行网关。游戏文档跑在 iframe sandbox="allow-scripts" 的不透明来源里,
|
||||
# 离开页面即随 iframe 卸载,因此不再要求独立发行域名与通配证书。
|
||||
location ~ "^/games/(?<game_id>game_[0-9a-f]{32})(?<game_path>/.*)?$" {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
proxy_set_header Cookie "";
|
||||
proxy_pass http://genarrative_api/api/game-distribution/releases/$game_id$game_path;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
}
|
||||
|
||||
# BEGIN GENARRATIVE MAIN SPA ROUTES
|
||||
location = / {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project)/?$" {
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project|components|design-system|games|games/detail|games/mine|games/play|games/publish)/?$" {
|
||||
try_files $uri /index.html =404;
|
||||
}
|
||||
# END GENARRATIVE MAIN SPA ROUTES
|
||||
|
||||
Vendored
+3
-4
@@ -179,10 +179,9 @@ GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ACCESS_KEY_ID=
|
||||
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ACCESS_KEY_SECRET=
|
||||
GENARRATIVE_AGC_PROJECT_SNAPSHOT_CHANNEL=dev
|
||||
|
||||
# 游戏发行入口模板:审核通过时按 {gameId} 占位符派生每游戏独立来源地址,例如
|
||||
# https://{gameId}.games.example.com/。模板必须含 {gameId},生产未配置时审核通过
|
||||
# 直接失败;非生产未配置时回落 http://127.0.0.1:<API_PORT>/api/game-distribution/releases/{gameId}/。
|
||||
GENARRATIVE_GAME_DISTRIBUTION_RELEASE_ENTRY_TEMPLATE=
|
||||
# 游戏发行入口固定为平台同源路径 /games/{gameId}/:审核通过时由 api-server 自己派生,
|
||||
# 不需要部署侧配置发行域名或通配证书;边缘由 nginx 的
|
||||
# genarrative-game-distribution-path.conf 把该路径映射到发行网关。
|
||||
|
||||
# SpacetimeDB 数据目录 OSS 冷备份配置。可由 cron / Jenkins 调用发布包内 scripts/database-backup-to-oss.mjs。
|
||||
GENARRATIVE_DATABASE_BACKUP_DATA_DIR=/stdb
|
||||
|
||||
@@ -100,10 +100,11 @@ curl -sSI -H 'Accept-Encoding: br' \
|
||||
- br 可用时返回 `Content-Encoding: br`。
|
||||
- 响应头应包含 `Vary: Accept-Encoding`。
|
||||
|
||||
## 游戏发行来源(每游戏独立 origin)
|
||||
## 游戏发行来源(平台同源路径)
|
||||
|
||||
- `deploy/nginx/genarrative-release-origin.conf` 为已公开游戏提供每游戏独立来源:`https://<gameId>.games.example.com/`。部署前替换域名、通配证书路径与 upstream 端口,并为 `*.games.example.com` 配置通配 DNS 与通配 TLS。
|
||||
- 该来源只把子域根路径映射到 `…/releases/<gameId>/index.html`、其余路径映射到 `…/releases/<gameId>/<原路径>`;平台 API、后台、SPA 与上传接口都不在这个来源上暴露,命中即 404。
|
||||
- 发行来源不使用 Cookie:带 `Cookie` 的请求在边缘直接 403,转发前也会 `proxy_set_header Cookie ""`。响应头(`X-Content-Type-Options`、CORP、无凭据 CORS、HTML CSP、内容类型白名单与 `Cache-Control: public, max-age=60, must-revalidate`)由 `api-server` 发行网关设置,边缘不覆盖。
|
||||
- 审核通过时 `api-server` 按部署模板(`GENARRATIVE_GAME_DISTRIBUTION_RELEASE_ENTRY_TEMPLATE=https://{gameId}.games.example.com/`)与 gameId 派生 `entryUrl`,即该子域根地址;换版本或下架只改变后端公开投影,边缘不需要改配置。
|
||||
- 门禁:`npm run check:release-origin-config` 会逐条校验模板约束、交叉检查发行网关仍在设置上述响应头,并在本机存在 `nginx` 与 `openssl` 时用自签通配证书渲染一份临时配置执行 `nginx -t`。
|
||||
- 现役发行入口是平台同源路径 `https://<平台域名>/games/<gameId>/`。三份常驻模板(`genarrative.conf`、`genarrative-dev-http.conf`、容器 `deploy/container/nginx.conf`)都内联同一条同源发行入口 location,把 `/games/<gameId>/` 与 `/games/<gameId>/<asset>` 转发到 `api-server` 发行网关;不再需要独立发行域名、`*.games.<域名>` 通配 DNS 或通配 TLS。
|
||||
- 该 location 的正则必须整体加双引号:`location ~ "^/games/(?<game_id>game_[0-9a-f]{32})(?<game_path>/.*)?$"`。不加引号时 nginx 会把 `{32}` 当块定界符,`nginx -t` 报 `pcre2_compile() failed: missing closing parenthesis`。
|
||||
- 发行入口不使用 Cookie:边缘转发前设置 `proxy_set_header Cookie ""`;`api-server` 发行网关也会拒绝带 Cookie 的请求。响应头(`X-Content-Type-Options`、CORP、无凭据 CORS、HTML CSP、内容类型白名单与 `Cache-Control: public, max-age=60, must-revalidate`)由 `api-server` 发行网关设置,边缘不覆盖。
|
||||
- 隔离靠 iframe 沙箱而不是独立来源:游戏文档跑在 `sandbox="allow-scripts"` 的不透明来源里,读不到主站 Cookie、storage 与 DOM,离开页面即随 iframe 卸载。
|
||||
- 审核通过时 `api-server` 按 gameId 派生同源路径 `/games/<gameId>/` 作为 `entryUrl` 写入公开投影,部署侧不再需要配置发行域名。换版本或下架只改变后端公开投影,边缘不需要改配置。
|
||||
- 门禁:`npm run check:nginx-spa-routes` 校验三份模板的 SPA allowlist(含 `/games`、`/games/detail`、`/games/play`、`/games/mine`、`/games/publish`)。历史上的独立来源模板与专属门禁已随同源方案上线删除。
|
||||
|
||||
@@ -179,6 +179,22 @@ server {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# 平台同源路径发行入口:/games/<gameId>/ 与 /games/<gameId>/<asset> 映射到
|
||||
# api-server 发行网关。游戏文档跑在 iframe sandbox="allow-scripts" 的不透明来源里,
|
||||
# 离开页面即随 iframe 卸载,因此不再要求独立发行域名与通配证书。
|
||||
location ~ "^/games/(?<game_id>game_[0-9a-f]{32})(?<game_path>/.*)?$" {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
proxy_set_header Cookie "";
|
||||
proxy_pass http://genarrative_api/api/game-distribution/releases/$game_id$game_path;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
}
|
||||
|
||||
# BEGIN GENARRATIVE MAIN SPA ROUTES
|
||||
location = / {
|
||||
error_page 503 /maintenance.html;
|
||||
@@ -190,7 +206,7 @@ server {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project)/?$" {
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project|components|design-system|games|games/detail|games/mine|games/play|games/publish)/?$" {
|
||||
error_page 503 /maintenance.html;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# 游戏发行来源(每游戏独立 origin)
|
||||
#
|
||||
# 部署前替换:
|
||||
# 1) `games.example.com` 为真实发行域,并为 `*.games.example.com` 配置通配 DNS
|
||||
# 与通配 TLS 证书;
|
||||
# 2) `ssl_certificate` / `ssl_certificate_key` 指向该通配证书;
|
||||
# 3) upstream 端口与 api-server 实际监听一致。
|
||||
#
|
||||
# 设计约定:
|
||||
# - 每个已公开游戏使用自己的子域:`https://<gameId>.games.example.com/`;
|
||||
# - 该来源只把请求映射到发行网关
|
||||
# `/api/game-distribution/releases/<gameId>/…`,平台 API、后台、SPA 与上传
|
||||
# 接口都不在这个来源上暴露;
|
||||
# - 发行来源从不使用 Cookie:带 Cookie 的请求直接 403,转发前也会清空 Cookie;
|
||||
# - `X-Content-Type-Options` / CORP / 无凭据 CORS / HTML CSP / 内容类型白名单由
|
||||
# api-server 发行网关设置,这里不覆盖,避免两层策略漂移;
|
||||
# - 公开版本切换与下架由后端 `publication_revision` CAS 决定,边缘只做按主机映射。
|
||||
|
||||
upstream genarrative_release_api {
|
||||
server 127.0.0.1:8082;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name ~^(?<game_id>[a-z0-9_]+)\.games\.example\.com$;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ~^(?<game_id>[a-z0-9_]+)\.games\.example\.com$;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/games.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/games.example.com/privkey.pem;
|
||||
|
||||
access_log /var/log/nginx/genarrative-release.access.log;
|
||||
error_log /var/log/nginx/genarrative-release.error.log warn;
|
||||
|
||||
# 发行文件是公开静态资源,从不携带平台 Cookie。带上 Cookie 的请求说明它落在
|
||||
# 平台会话来源上,直接拒绝,避免发行内容被主站同源脚本读取。
|
||||
if ($http_cookie) {
|
||||
return 403;
|
||||
}
|
||||
|
||||
# 子域根路径直接服务该游戏的 index.html,游戏内其余资源按相对路径原样交给
|
||||
# 发行网关;审核通过时 api-server 按发行入口模板派生的 entryUrl 就是
|
||||
# https://<gameId>.games.example.com/。
|
||||
location = / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
proxy_set_header Cookie "";
|
||||
proxy_pass http://genarrative_release_api/api/game-distribution/releases/$game_id/index.html;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
proxy_set_header Cookie "";
|
||||
proxy_pass http://genarrative_release_api/api/game-distribution/releases/$game_id$request_uri;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
}
|
||||
}
|
||||
@@ -199,6 +199,22 @@ server {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# 平台同源路径发行入口:/games/<gameId>/ 与 /games/<gameId>/<asset> 映射到
|
||||
# api-server 发行网关。游戏文档跑在 iframe sandbox="allow-scripts" 的不透明来源里,
|
||||
# 离开页面即随 iframe 卸载,因此不再要求独立发行域名与通配证书。
|
||||
location ~ "^/games/(?<game_id>game_[0-9a-f]{32})(?<game_path>/.*)?$" {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
proxy_set_header Cookie "";
|
||||
proxy_pass http://genarrative_api/api/game-distribution/releases/$game_id$game_path;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
}
|
||||
|
||||
# BEGIN GENARRATIVE MAIN SPA ROUTES
|
||||
location = / {
|
||||
error_page 503 /maintenance.html;
|
||||
@@ -210,7 +226,7 @@ server {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project)/?$" {
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project|components|design-system|games|games/detail|games/mine|games/play|games/publish)/?$" {
|
||||
error_page 503 /maintenance.html;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
|
||||
Reference in New Issue
Block a user