修正 Pingora 高端口重定向验收

direct live 支持独立 redirect base URL 预期

release readiness 和切换证据链透传 redirect base URL

直连启用脚本透传高端口 rehearsal 参数

补充 direct live guard、运维门禁和文档示例
This commit is contained in:
2026-06-17 23:00:28 +08:00
parent 764f3609d0
commit 75b85ee5a9
10 changed files with 186 additions and 5 deletions
+4 -1
View File
@@ -28,9 +28,12 @@ GENARRATIVE_PINGORA_DIRECT_HTTP_BASE_URL=http://127.0.0.1
# 本机直连 127.0.0.1 时必须设置正式域名;direct live 会同时用作 Host header 和 TLS SNI。release readiness --require-direct 必须提供。只填域名或 host:port,不要填 https:// URL。
GENARRATIVE_PINGORA_DIRECT_HOST=
# 单独 direct live 可选;release readiness --require-direct 必须提供,用于显式校验 HTTP redirect Location 的 host。只填域名或 host:port,非标准端口本机 smoke 可设为 127.0.0.1:18443
# 单独 direct live 可选;release readiness --require-direct 必须提供,用于显式校验 HTTP redirect Location 的 host。只填域名或 host:port。
GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST=
# 可选:高端口 rehearsal 访问 127.0.0.1:18443、但期望 Location 指向正式域名默认 HTTPS 入口时设置;只能填 https:// base URL,不要带路径、查询或片段。
GENARRATIVE_PINGORA_DIRECT_REDIRECT_BASE_URL=
# 可选:配置后会额外检查 /__genarrative_pingora/healthz 内部探针。
GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN=
File diff suppressed because one or more lines are too long
@@ -472,7 +472,7 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
4. 使用 `deploy/nginx/snippets/genarrative-pingora-canary.conf` 做 Nginx 前缀 canary;启用前先跑 `npm run check:nginx-pingora-canary``npm run check:pingora-canary-docker`,有 Nginx 或 Docker 的目标环境分别强制跑 `node scripts/check-nginx-pingora-canary.mjs --require-nginx``node scripts/check-pingora-canary-docker.mjs --require-docker --pull`,其中 Docker handoff 会自动对账临时 Nginx 与 Pingora access log。启用后跑 `npm run check:pingora-canary-live`,再用 current release 随包 access log parity 脚本对账目标机 Nginx 与 Pingora access log。canary live 的 base URL、prefix、Host、额外 path 和 timeout 不能包含换行或 NULcanary live timeout 和 access log `since-lines` 必须是正整数,非法值直接失败。
5. 前缀 canary 通过后,再使用 `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 做真实路径 canary;它必须作为独立本机 `server` include 到 Nginx `http` 上下文,不能 include 到生产 `443` server 内。启用后跑 `node scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再用 current release 随包 access log parity 脚本传 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log` 对账 `/api/creation-entry/config``/v1/identity``/assets/app.js` 等真实路径。
6. 目标机 canary include 后必须跑正式切换聚合门禁:源码 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`;目标机 current release 执行 `/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 时,两种命令都追加 `--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`。缺少 Host 会直接失败,避免 live canary 误测默认 vhostlive smoke 后还会按 `request_id` 对账 Nginx 与 Pingora access log,缺少同一请求的 Pingora 日志、状态码、方法或 path 漂移都会失败。
7. 如需评估 Pingora 直连公网入口,必须显式配置 `TLS_LISTEN`、证书、私钥和 `HTTP_REDIRECT_LISTEN`Certbot 证书先用随包 `scripts/deploy/pingora-tls-cert-sync.mjs` 同步到 `/etc/genarrative/pingora-tls/<域名>/`,不要直接 chmod Lets Encrypt live/archive 原路径;绑定 `80/443` 时还必须人工启用 `genarrative-pingora-gateway-direct-entry.conf` drop-in 授予 `CAP_NET_BIND_SERVICE`。随后用 `npm run check:pingora-gateway-smoke` 覆盖 TLS / HTTP/2 ALPN / redirect / WSS subscribe;目标机必须先跑 `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` 或 release readiness 的 `--require-direct`,且 `--require-direct` 必须带 direct HTTPS base URL、direct HTTP base URL、正式域名 Host/SNI、redirect Location host、Pingora access log 文件、health patrol env 文件、direct preflight env 文件、systemd drop-in 生效检查、service EnvironmentFile 一致性检查、当前用户和 systemd 服务用户证书可读检查、service 二进制可执行检查、端口释放检查和显式 SpacetimeDB 数据库名,并会拒绝 `--skip-wss`。direct live 会用生成的 `request_id` 反查 Pingora access log;缺少对应日志、method 漂移、path 漂移或 status 漂移都算直连门禁失败。direct preflight 会拒绝开启网关保护但未确认共享保护层的 `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1` 配置;`--env-file``--systemd-service`、服务用户和 env 中的 listen / cert / key 值都不能包含换行或 NUL,执行 `systemctl cat``sudo -u <serviceUser> test -r <file>` 前还会复核子命令参数,避免污染参数进入目标机预检命令;direct live 的 URL、Host、probe token、额外 path、数据库名、access log 路径、timeout 和布尔 env 也不能包含换行或 NUL,且会在发起请求前失败;direct live timeout 必须是正整数,直连相关布尔 env 只接受 `true/false``1/0``yes/no``on/off` 或空值,非法值直接失败。证书申请与续期仍由 Certbot / 外部自动化承担,网关只读取现有文件。
7. 如需评估 Pingora 直连公网入口,必须显式配置 `TLS_LISTEN`、证书、私钥和 `HTTP_REDIRECT_LISTEN`Certbot 证书先用随包 `scripts/deploy/pingora-tls-cert-sync.mjs` 同步到 `/etc/genarrative/pingora-tls/<域名>/`,不要直接 chmod Lets Encrypt live/archive 原路径;绑定 `80/443` 时还必须人工启用 `genarrative-pingora-gateway-direct-entry.conf` drop-in 授予 `CAP_NET_BIND_SERVICE`。随后用 `npm run check:pingora-gateway-smoke` 覆盖 TLS / HTTP/2 ALPN / redirect / WSS subscribe;目标机必须先跑 `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` 或 release readiness 的 `--require-direct`,且 `--require-direct` 必须带 direct HTTPS base URL、direct HTTP base URL、正式域名 Host/SNI、redirect Location host、Pingora access log 文件、health patrol env 文件、direct preflight env 文件、systemd drop-in 生效检查、service EnvironmentFile 一致性检查、当前用户和 systemd 服务用户证书可读检查、service 二进制可执行检查、端口释放检查和显式 SpacetimeDB 数据库名,并会拒绝 `--skip-wss`高端口 rehearsal 使用 `https://127.0.0.1:<高端口>` 打入但期望 HTTP redirect Location 指向正式域名默认 HTTPS 入口时,额外传 `--direct-redirect-base-url https://<域名>``--direct-redirect-host` 仍必须保留,用于 runbook Host 一致性约束。direct live 会用生成的 `request_id` 反查 Pingora access log;缺少对应日志、method 漂移、path 漂移或 status 漂移都算直连门禁失败。direct preflight 会拒绝开启网关保护但未确认共享保护层的 `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1` 配置;`--env-file``--systemd-service`、服务用户和 env 中的 listen / cert / key 值都不能包含换行或 NUL,执行 `systemctl cat``sudo -u <serviceUser> test -r <file>` 前还会复核子命令参数,避免污染参数进入目标机预检命令;direct live 的 URL、Host、redirect base URL、probe token、额外 path、数据库名、access log 路径、timeout 和布尔 env 也不能包含换行或 NUL,且会在发起请求前失败;direct live timeout 必须是正整数,直连相关布尔 env 只接受 `true/false``1/0``yes/no``on/off` 或空值,非法值直接失败。证书申请与续期仍由 Certbot / 外部自动化承担,网关只读取现有文件。
8. 正式直连 runbook 的启用前基础门禁和启用后 `--require-direct` 复核必须调用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only`。该脚本、`scripts/check-pingora-canary-live.mjs``deploy/nginx/` 必须进入生产 API release、Jenkins API Build 归档、Jenkins API Deploy 复制清单和目标机 current release;缺失时部署应 fail-fast,切换窗口不能依赖源码 checkout 或 Jenkins workspace。
9. direct preflight / direct live timeout 必须是正整数;直连相关布尔 env 和 Pingora gateway env 里的 `TRUST_X_FORWARDED_FOR` / `TRUSTED_FRONT_PROXY_CONFIRMED` 只接受 `true/false``1/0``yes/no``on/off` 或空值,非法值直接失败。
10. Pingora 正式化口径固定为 gzip-onlyBrotli 继续由 Nginx / 前置代理承担,直连 Pingora 不以 Brotli parity 作为切换门禁。如需多实例,开启网关接流保护时必须先引入共享限流 / 共享并发保护层并设置 `GENARRATIVE_PINGORA_GATEWAY_SHARED_PROTECTION_CONFIRMED=true`,否则保持 `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT=1`;关闭网关保护的多实例方案必须明确由前置 Nginx / LB 承担全局限流。
+10
View File
@@ -558,6 +558,11 @@ function assertScriptShape() {
'--apply 必须同时提供 --direct-host',
'启用脚本 apply 时必须强制正式域名 Host/SNI 参数。',
);
assertIncludes(
content,
'--direct-redirect-base-url',
'启用脚本必须支持 direct live redirect base URL 覆盖,用于高端口 rehearsal。',
);
assertIncludes(
content,
'--apply 必须同时提供 --direct-redirect-host',
@@ -653,6 +658,11 @@ function assertScriptShape() {
'print_redacted_command node "${direct_live_args[@]}"',
'启用脚本展示 direct live smoke 命令时必须隐藏 probe token。',
);
assertIncludes(
content,
'direct_live_args+=(--redirect-base-url "${DIRECT_REDIRECT_BASE_URL}")',
'启用脚本必须把 direct redirect base URL 透传给 direct live smoke。',
);
assertIncludes(
content,
'drop-in 目录不能是符号链接',
@@ -50,6 +50,13 @@ function main() {
'--redirect-host',
'example.com\nhidden',
]);
assertRejectsControlCharacter('--redirect-base-url', [
'--https-base-url',
'https://127.0.0.1',
'--redirect-base-url',
'https://example.com\nhidden',
]);
assertRejectsInvalidRedirectBaseUrl();
assertRejectsControlCharacter('--probe-token', [
'--https-base-url',
'https://127.0.0.1',
@@ -87,6 +94,33 @@ function main() {
]);
}
function assertRejectsInvalidRedirectBaseUrl() {
const result = spawnSync(
'node',
[
DIRECT_LIVE_SCRIPT,
'--https-base-url',
'https://127.0.0.1',
'--redirect-base-url',
'http://example.com',
],
{
cwd: process.cwd(),
encoding: 'utf8',
env: process.env,
},
);
if ((result.status ?? 0) === 0) {
failures.push('--redirect-base-url 必须拒绝非 HTTPS base URL。');
return;
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--redirect-base-url 必须使用 https://',
'--redirect-base-url 非 HTTPS 时必须给出明确错误。',
);
}
function assertRejectsControlCharacter(label, args) {
const result = spawnSync('node', [DIRECT_LIVE_SCRIPT, ...args], {
cwd: process.cwd(),
+34
View File
@@ -41,6 +41,8 @@ Options:
--http-base-url <url> Optional HTTP base URL that should 301 to HTTPS.
--host <host> Optional Host header for local --resolve style checks.
--redirect-host <host> Optional expected Location host for HTTP redirects.
--redirect-base-url <url>
Optional expected HTTPS base URL for HTTP redirects.
--probe-token <token> Optional Pingora shadow probe token.
--spacetime-database <name>
SpacetimeDB database for WSS subscribe smoke, default genarrative-prod.
@@ -60,6 +62,7 @@ Environment aliases:
GENARRATIVE_PINGORA_DIRECT_HTTP_BASE_URL
GENARRATIVE_PINGORA_DIRECT_HOST
GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST
GENARRATIVE_PINGORA_DIRECT_REDIRECT_BASE_URL
GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN
GENARRATIVE_PINGORA_DIRECT_SPACETIME_DATABASE
GENARRATIVE_PINGORA_DIRECT_REQUIRE_WSS_UPGRADE
@@ -77,6 +80,8 @@ function parseArgs(argv) {
httpBaseUrl: process.env.GENARRATIVE_PINGORA_DIRECT_HTTP_BASE_URL || '',
host: process.env.GENARRATIVE_PINGORA_DIRECT_HOST || '',
redirectHost: process.env.GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST || '',
redirectBaseUrl:
process.env.GENARRATIVE_PINGORA_DIRECT_REDIRECT_BASE_URL || '',
probeToken: process.env.GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN || '',
spacetimeDatabase:
process.env.GENARRATIVE_PINGORA_DIRECT_SPACETIME_DATABASE ||
@@ -133,6 +138,9 @@ function parseArgs(argv) {
case '--redirect-host':
result.redirectHost = requireValue(argv, ++index, arg);
break;
case '--redirect-base-url':
result.redirectBaseUrl = requireValue(argv, ++index, arg);
break;
case '--probe-token':
result.probeToken = requireValue(argv, ++index, arg);
break;
@@ -197,6 +205,9 @@ function parseArgs(argv) {
if (result.redirectHost) {
validateHostOption(result.redirectHost, '--redirect-host');
}
if (result.redirectBaseUrl) {
validateHttpsBaseUrl(result.redirectBaseUrl, '--redirect-base-url');
}
if (result.probeToken) {
validateNoControlCharacters(result.probeToken, '--probe-token');
}
@@ -255,6 +266,26 @@ function validateHostOption(value, flag) {
}
}
function validateHttpsBaseUrl(value, flag) {
validateNoControlCharacters(value, flag);
const parsed = new URL(value);
if (parsed.protocol !== 'https:') {
throw new Error(`${flag} 必须使用 https://`);
}
if (
!parsed.hostname ||
parsed.pathname !== '/' ||
parsed.search ||
parsed.hash ||
parsed.username ||
parsed.password
) {
throw new Error(
`${flag} 只能是 HTTPS base URL,不能包含路径、查询、片段或认证信息。`,
);
}
}
function validateSafeAbsoluteFilePath(filePath, label) {
validateNoControlCharacters(filePath, label);
if (!nodePath.isAbsolute(filePath)) {
@@ -1065,6 +1096,9 @@ function assertPingoraGatewayHeader(check, response) {
}
function directHttpsLocation(httpsBaseUrl, path) {
if (config.redirectBaseUrl) {
return joinUrl(config.redirectBaseUrl, path);
}
if (!config.redirectHost) {
return joinUrl(httpsBaseUrl, path);
}
@@ -86,6 +86,8 @@ function parseArgs(argv) {
directHost: process.env.GENARRATIVE_PINGORA_DIRECT_HOST || '',
directRedirectHost:
process.env.GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST || '',
directRedirectBaseUrl:
process.env.GENARRATIVE_PINGORA_DIRECT_REDIRECT_BASE_URL || '',
directProbeToken: process.env.GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN || '',
directPingoraAccessLog:
process.env.GENARRATIVE_PINGORA_DIRECT_PINGORA_ACCESS_LOG || '',
@@ -228,6 +230,9 @@ function parseArgs(argv) {
case '--direct-redirect-host':
result.directRedirectHost = requireValue(argv, ++index, arg);
break;
case '--direct-redirect-base-url':
result.directRedirectBaseUrl = requireValue(argv, ++index, arg);
break;
case '--direct-probe-token':
result.directProbeToken = requireValue(argv, ++index, arg);
break;
@@ -412,6 +417,24 @@ function parseArgs(argv) {
if (result.directRedirectHost) {
validateHostOption(result.directRedirectHost, '--direct-redirect-host');
}
if (result.directRedirectBaseUrl) {
validateHttpUrl(result.directRedirectBaseUrl, '--direct-redirect-base-url');
const parsed = new URL(result.directRedirectBaseUrl);
if (parsed.protocol !== 'https:') {
throw new Error('--direct-redirect-base-url 必须使用 https://。');
}
if (
parsed.pathname !== '/' ||
parsed.search ||
parsed.hash ||
parsed.username ||
parsed.password
) {
throw new Error(
'--direct-redirect-base-url 只能是 HTTPS base URL,不能包含路径、查询、片段或认证信息。',
);
}
}
if (result.requireDirect && !result.directPingoraAccessLog) {
throw new Error(
'启用 --require-direct 时必须提供 --direct-pingora-access-log 或 GENARRATIVE_PINGORA_DIRECT_PINGORA_ACCESS_LOG',
@@ -661,6 +684,8 @@ Options:
--direct-http-base-url <url> direct HTTP redirect base URL;也可用 GENARRATIVE_PINGORA_DIRECT_HTTP_BASE_URL。
--direct-host <host> direct live Host header 和 TLS SNI;也可用 GENARRATIVE_PINGORA_DIRECT_HOST。--require-direct 时必须显式提供。
--direct-redirect-host <host> direct redirect Location host;也可用 GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST。--require-direct 时必须显式提供,cutover runbook 中必须和 --direct-host 使用同一 hostname。
--direct-redirect-base-url <url>
可选 direct redirect Location HTTPS base URL;用于高端口 rehearsal 访问 127.0.0.1:18443 但期望 Location 指向正式 HTTPS 入口。
--direct-probe-token <token> direct live shadow probe token;也可用 GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN。
--direct-pingora-access-log <path>
direct live 后 Pingora access log 落盘校验路径;--require-direct 时必须显式提供。
@@ -971,6 +996,9 @@ function buildCutoverPlan(config) {
config.directHost,
'--direct-redirect-host',
config.directRedirectHost,
...(config.directRedirectBaseUrl
? ['--direct-redirect-base-url', config.directRedirectBaseUrl]
: []),
'--direct-spacetime-database',
config.directSpacetimeDatabase,
'--direct-pingora-access-log',
@@ -1023,6 +1051,7 @@ function buildCutoverPlan(config) {
`cutover-run-id=${config.cutoverRunId}`,
`direct-host=${config.directHost}`,
`direct-redirect-host=${config.directRedirectHost}`,
`direct-redirect-base-url=${config.directRedirectBaseUrl || '-'}`,
`health-patrol-public-host=${config.directHost}`,
`rollback-nginx-smoke-host=${config.rollbackNginxSmokeHost}`,
`rollback-nginx-smoke-expect-body=${config.rollbackNginxSmokeExpectBody}`,
@@ -1229,6 +1258,9 @@ function buildCutoverPlan(config) {
config.directHost,
'--direct-redirect-host',
config.directRedirectHost,
...(config.directRedirectBaseUrl
? ['--direct-redirect-base-url', config.directRedirectBaseUrl]
: []),
...(config.directProbeToken
? ['--direct-probe-token', config.directProbeToken]
: []),
@@ -1485,6 +1517,9 @@ function readinessArgs(config, { includeDirect }) {
config.directHost,
'--direct-redirect-host',
config.directRedirectHost,
...(config.directRedirectBaseUrl
? ['--direct-redirect-base-url', config.directRedirectBaseUrl]
: []),
...(config.directProbeToken
? ['--direct-probe-token', config.directProbeToken]
: []),
@@ -1718,6 +1753,9 @@ function buildSteps(config) {
...(config.directRedirectHost
? ['--redirect-host', config.directRedirectHost]
: []),
...(config.directRedirectBaseUrl
? ['--redirect-base-url', config.directRedirectBaseUrl]
: []),
...(config.directProbeToken
? ['--probe-token', config.directProbeToken]
: []),
@@ -1910,6 +1948,9 @@ function appendTargetDirectSteps(steps, config, scriptPath) {
...(config.directRedirectHost
? ['--redirect-host', config.directRedirectHost]
: []),
...(config.directRedirectBaseUrl
? ['--redirect-base-url', config.directRedirectBaseUrl]
: []),
...(config.directProbeToken
? ['--probe-token', config.directProbeToken]
: []),
@@ -2933,6 +2933,12 @@ const checks = [
reason:
'Pingora direct live smoke 环境示例必须暴露 HTTP redirect Location host。',
},
{
file: 'deploy/env/pingora-direct-live.env.example',
includes: 'GENARRATIVE_PINGORA_DIRECT_REDIRECT_BASE_URL',
reason:
'Pingora direct live smoke 环境示例必须暴露高端口 rehearsal 的 redirect Location base URL 覆盖。',
},
{
file: 'deploy/env/pingora-direct-live.env.example',
includes: '只填域名或 host:port',
@@ -3106,6 +3112,12 @@ const checks = [
reason:
'Pingora direct live smoke 必须提前拒绝非法 redirect Location host 参数。',
},
{
file: 'scripts/check-pingora-direct-live.mjs',
includes: 'validateHttpsBaseUrl(result.redirectBaseUrl,',
reason:
'Pingora direct live smoke 必须提前拒绝非法 redirect Location base URL 参数。',
},
{
file: 'scripts/check-pingora-direct-live.mjs',
includes: 'parseRequiredPositiveInt',
@@ -3638,6 +3650,12 @@ const checks = [
includes: '--apply 必须同时提供 --direct-redirect-host',
reason: 'Pingora 直连启用 apply 后必须验证 redirect Location host。',
},
{
file: 'scripts/deploy/pingora-direct-enable.sh',
includes: '--direct-redirect-base-url',
reason:
'Pingora 直连启用脚本必须支持把高端口 rehearsal 的 redirect base URL 透传给 direct live smoke。',
},
{
file: 'scripts/deploy/pingora-direct-enable.sh',
includes: '--apply 必须同时提供 --direct-spacetime-database',
+15 -2
View File
@@ -26,6 +26,7 @@ DIRECT_HTTPS_BASE_URL="${GENARRATIVE_PINGORA_DIRECT_HTTPS_BASE_URL:-}"
DIRECT_HTTP_BASE_URL="${GENARRATIVE_PINGORA_DIRECT_HTTP_BASE_URL:-}"
DIRECT_HOST="${GENARRATIVE_PINGORA_DIRECT_HOST:-}"
DIRECT_REDIRECT_HOST="${GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST:-}"
DIRECT_REDIRECT_BASE_URL="${GENARRATIVE_PINGORA_DIRECT_REDIRECT_BASE_URL:-}"
DIRECT_PROBE_TOKEN="${GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN:-}"
DIRECT_PINGORA_ACCESS_LOG="${GENARRATIVE_PINGORA_DIRECT_PINGORA_ACCESS_LOG:-}"
DIRECT_ACCESS_LOG_SINCE_LINES="${GENARRATIVE_PINGORA_DIRECT_ACCESS_LOG_SINCE_LINES:-}"
@@ -39,7 +40,7 @@ usage() {
cat <<'EOF'
用法:
scripts/deploy/pingora-direct-enable.sh [--apply] [--preflight-env-file <path>] [--preflight-systemd] [--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 <url>] [--direct-http-base-url <url>] [--direct-host <host>] [--direct-redirect-host <host>]
[--direct-https-base-url <url>] [--direct-http-base-url <url>] [--direct-host <host>] [--direct-redirect-host <host>] [--direct-redirect-base-url <url>]
[--direct-spacetime-database <name>] [--direct-probe-token <token>] [--direct-pingora-access-log <path>] [--direct-access-log-since-lines <count>] [--direct-timeout-ms <ms>] [--direct-live-script <path>] [--no-postcheck]
[--current-release-audit-script <path>] [--current-release-root <path>] [--current-release-audit-timeout-ms <ms>]
[--service <name>] [--service-unit-path <path>] [--template-path <path>] [--dropin-path <path>]
@@ -211,6 +212,14 @@ while [[ $# -gt 0 ]]; do
fi
shift 2
;;
--direct-redirect-base-url)
DIRECT_REDIRECT_BASE_URL="${2:-}"
if [[ -z "${DIRECT_REDIRECT_BASE_URL}" ]]; then
echo "[pingora-direct-enable] --direct-redirect-base-url 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-probe-token)
DIRECT_PROBE_TOKEN="${2:-}"
if [[ -z "${DIRECT_PROBE_TOKEN}" ]]; then
@@ -297,6 +306,7 @@ reject_control_characters "--direct-https-base-url" "${DIRECT_HTTPS_BASE_URL}"
reject_control_characters "--direct-http-base-url" "${DIRECT_HTTP_BASE_URL}"
reject_control_characters "--direct-host" "${DIRECT_HOST}"
reject_control_characters "--direct-redirect-host" "${DIRECT_REDIRECT_HOST}"
reject_control_characters "--direct-redirect-base-url" "${DIRECT_REDIRECT_BASE_URL}"
reject_control_characters "--direct-probe-token" "${DIRECT_PROBE_TOKEN}"
reject_control_characters "--direct-pingora-access-log" "${DIRECT_PINGORA_ACCESS_LOG}"
reject_control_characters "--direct-access-log-since-lines" "${DIRECT_ACCESS_LOG_SINCE_LINES}"
@@ -732,6 +742,9 @@ run_direct_live_smoke_after_enable() {
if [[ -n "${DIRECT_REDIRECT_HOST}" ]]; then
direct_live_args+=(--redirect-host "${DIRECT_REDIRECT_HOST}")
fi
if [[ -n "${DIRECT_REDIRECT_BASE_URL}" ]]; then
direct_live_args+=(--redirect-base-url "${DIRECT_REDIRECT_BASE_URL}")
fi
if [[ -n "${DIRECT_PROBE_TOKEN}" ]]; then
direct_live_args+=(--probe-token "${DIRECT_PROBE_TOKEN}")
fi
@@ -905,5 +918,5 @@ cat <<EOF
- systemctl is-active ${SERVICE_NAME} 为 active
- node ${CURRENT_RELEASE_AUDIT_SCRIPT} --release-root ${CURRENT_RELEASE_ROOT} --require-pingora-gateway --systemd-show --systemd-service ${SERVICE_NAME} 已通过
- 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 已通过
- node ${DIRECT_LIVE_SCRIPT} --https-base-url ${DIRECT_HTTPS_BASE_URL:-<direct-https-base-url>} --http-base-url ${DIRECT_HTTP_BASE_URL:-<direct-http-base-url>} --host ${DIRECT_HOST:-<direct-host>} --redirect-host ${DIRECT_REDIRECT_HOST:-<direct-redirect-host>} --pingora-access-log ${DIRECT_PINGORA_ACCESS_LOG:-<pingora-access-log>} --spacetime-database ${DIRECT_SPACETIME_DATABASE:-<database>} --require-wss-upgrade --json 已通过,且 direct-access-log 结构化证据 matched=checked、missing=0、mismatches=0
- node ${DIRECT_LIVE_SCRIPT} --https-base-url ${DIRECT_HTTPS_BASE_URL:-<direct-https-base-url>} --http-base-url ${DIRECT_HTTP_BASE_URL:-<direct-http-base-url>} --host ${DIRECT_HOST:-<direct-host>} --redirect-host ${DIRECT_REDIRECT_HOST:-<direct-redirect-host>} ${DIRECT_REDIRECT_BASE_URL:+--redirect-base-url ${DIRECT_REDIRECT_BASE_URL} }--pingora-access-log ${DIRECT_PINGORA_ACCESS_LOG:-<pingora-access-log>} --spacetime-database ${DIRECT_SPACETIME_DATABASE:-<database>} --require-wss-upgrade --json 已通过,且 direct-access-log 结构化证据 matched=checked、missing=0、mismatches=0
EOF
@@ -442,6 +442,8 @@ function parseArgs(argv) {
directHost: process.env.GENARRATIVE_PINGORA_DIRECT_HOST || '',
directRedirectHost:
process.env.GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST || '',
directRedirectBaseUrl:
process.env.GENARRATIVE_PINGORA_DIRECT_REDIRECT_BASE_URL || '',
directProbeToken: process.env.GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN || '',
directSpacetimeDatabase:
process.env.GENARRATIVE_PINGORA_DIRECT_SPACETIME_DATABASE || '',
@@ -521,6 +523,9 @@ function parseArgs(argv) {
case '--direct-redirect-host':
result.directRedirectHost = requireValue(argv, ++index, arg);
break;
case '--direct-redirect-base-url':
result.directRedirectBaseUrl = requireValue(argv, ++index, arg);
break;
case '--direct-probe-token':
result.directProbeToken = requireValue(argv, ++index, arg);
break;
@@ -597,6 +602,7 @@ Options:
--direct-http-base-url <url> 透传给 direct live smoke 的 HTTP redirect / ACME 入口。
--direct-host <host> 透传给 direct live smoke 的正式 Host/SNI。
--direct-redirect-host <host> 透传给 direct live smoke 的 redirect Location host。
--direct-redirect-base-url <url> 可选,透传给 direct live smoke 的 redirect Location HTTPS base URL。
--direct-probe-token <token> 可选,透传给 direct live smoke 的内部探针 token;证据中会脱敏。
--direct-spacetime-database <db> 透传给 direct live smoke 的 SpacetimeDB 数据库名。
--direct-pingora-access-log <path>
@@ -681,6 +687,9 @@ function validateDirectLiveConfig(config) {
throw new Error('--run-direct-live 必须提供 --direct-redirect-host。');
}
validateHostOption(config.directRedirectHost, '--direct-redirect-host');
if (config.directRedirectBaseUrl) {
validateHttpsBaseUrl(config.directRedirectBaseUrl, '--direct-redirect-base-url');
}
if (!config.directSpacetimeDatabase) {
throw new Error('--run-direct-live 必须提供 --direct-spacetime-database。');
}
@@ -807,6 +816,22 @@ function validateHttpsUrl(value, label) {
}
}
function validateHttpsBaseUrl(value, label) {
validateHttpsUrl(value, label);
const parsed = new URL(value);
if (
parsed.pathname !== '/' ||
parsed.search ||
parsed.hash ||
parsed.username ||
parsed.password
) {
throw new Error(
`${label} 只能是 HTTPS base URL,不能包含路径、查询、片段或认证信息。`,
);
}
}
function validateHttpOnlyUrl(value, label) {
validateHttpUrl(value, label);
if (new URL(value).protocol !== 'http:') {
@@ -961,6 +986,9 @@ function buildDirectLiveArgs(config) {
config.directHost,
'--redirect-host',
config.directRedirectHost,
...(config.directRedirectBaseUrl
? ['--redirect-base-url', config.directRedirectBaseUrl]
: []),
...(config.directProbeToken
? ['--probe-token', config.directProbeToken]
: []),