Files
Genarrative/scripts/check-nginx-pingora-canary.mjs
kdletters 071faa482c 统一 Rust 与 TypeScript 格式化门禁
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口

完成项目 TypeScript/Prettier 与 Rust 全量格式化

修复 Pingora expected executable 门禁的空白敏感误报

同步开发运维文档与 AGC skill pack 格式化忽略规则
2026-09-01 16:28:34 +08:00

531 lines
16 KiB
JavaScript
Raw Permalink 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.
#!/usr/bin/env node
import { execFileSync } from 'node:child_process';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
const PREFIX_SNIPPET_PATH =
'deploy/nginx/snippets/genarrative-pingora-canary.conf';
const REALPATH_SNIPPET_PATH =
'deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf';
const PROBE_TOKEN_PLACEHOLDER = '__GENARRATIVE_PINGORA_PROBE_TOKEN__';
const CANARY_PREFIX = '/__genarrative_pingora_canary';
const REALPATH_HEALTHZ_PATH = '/__genarrative_pingora_realpath_canary/healthz';
const PINGORA_UPSTREAM = 'http://127.0.0.1:18081';
const REALPATH_HANDOFF =
'add_header X-Genarrative-Nginx-Handoff pingora-realpath-canary always;';
const requireNginx = process.argv.includes('--require-nginx');
const explicitNginxBinary =
process.env.GENARRATIVE_CHECK_NGINX_BINARY?.trim() ?? '';
const nginxBinary = explicitNginxBinary || 'nginx';
const failures = [];
let activeSnippetPath = PREFIX_SNIPPET_PATH;
function fail(message) {
failures.push(message);
}
function countOccurrences(content, needle) {
return content.split(needle).length - 1;
}
function requireIncludes(content, needle, reason) {
if (!content.includes(needle)) {
fail(`${activeSnippetPath}: 缺少 ${needle}${reason}`);
}
}
function requireNotMatches(content, pattern, reason) {
if (pattern.test(content)) {
fail(`${activeSnippetPath}: ${reason}`);
}
}
function extractLocationBlocks(content) {
const blocks = new Map();
const locationPattern = /^\s*location\s+([^{]+)\{/gmu;
for (const match of content.matchAll(locationPattern)) {
const openBraceIndex = content.indexOf('{', match.index);
let depth = 0;
for (let index = openBraceIndex; index < content.length; index += 1) {
const char = content[index];
if (char === '{') {
depth += 1;
} else if (char === '}') {
depth -= 1;
if (depth === 0) {
const header = `location ${match[1].trim()}`;
const block = content.slice(match.index, index + 1);
const body = content.slice(openBraceIndex + 1, index);
blocks.set(header, { body, block });
break;
}
}
}
}
return blocks;
}
function requireLocation(blocks, header) {
const block = blocks.get(header);
if (!block) {
fail(`${activeSnippetPath}: 缺少 ${header} location。`);
}
return block;
}
function requireLocationIncludes(header, block, needle, reason) {
if (!block?.body.includes(needle)) {
fail(`${activeSnippetPath}: ${header} 缺少 ${needle}${reason}`);
}
}
function requireLocalOnlyAccess(header, block) {
for (const directive of ['allow 127.0.0.1;', 'allow ::1;', 'deny all;']) {
requireLocationIncludes(
header,
block,
directive,
'canary 入口默认必须只放行本机,避免误 include 后公开接流。',
);
}
}
function runStaticChecks(content) {
requireIncludes(
content,
PROBE_TOKEN_PLACEHOLDER,
'仓库模板必须保留 probe token 占位符,部署时再替换为非 Git 密钥。',
);
requireIncludes(
content,
`proxy_pass ${PINGORA_UPSTREAM}`,
'canary 必须固定转发到本机 Pingora shadow 端口。',
);
requireIncludes(
content,
'X-Genarrative-Nginx-Handoff',
'canary 响应必须带 handoff 标识,方便验证请求确实经 Nginx 交给 Pingora。',
);
requireNotMatches(
content,
/^\s*(http|server|listen)\b/mu,
'canary snippet 只能包含 location 片段,不能自行声明 http/server/listen。',
);
const locationBlocks = extractLocationBlocks(content);
if (locationBlocks.size !== 3) {
fail(
`${activeSnippetPath}: 预期只有 3 个 location,实际 ${locationBlocks.size} 个。`,
);
}
const healthzHeader = `location = ${CANARY_PREFIX}/healthz`;
const websocketHeader = `location ~ ^${CANARY_PREFIX}/v1/database/[^/]+/subscribe$`;
const genericHeader = `location ${CANARY_PREFIX}/`;
const healthz = requireLocation(locationBlocks, healthzHeader);
const websocket = requireLocation(locationBlocks, websocketHeader);
const generic = requireLocation(locationBlocks, genericHeader);
for (const [header, block] of [
[healthzHeader, healthz],
[websocketHeader, websocket],
[genericHeader, generic],
]) {
requireLocalOnlyAccess(header, block);
requireLocationIncludes(
header,
block,
'add_header X-Genarrative-Nginx-Handoff pingora-canary always;',
'每条 canary 路由都必须可通过响应头审计 handoff。',
);
requireLocationIncludes(
header,
block,
'proxy_set_header X-Request-Id $request_id;',
'canary 访问需要沿用 Nginx request_id,便于和 Pingora access log 对照。',
);
}
requireLocationIncludes(
healthzHeader,
healthz,
`proxy_set_header X-Genarrative-Pingora-Probe "${PROBE_TOKEN_PLACEHOLDER}";`,
'healthz handoff 必须带 shadow probe token。',
);
requireLocationIncludes(
healthzHeader,
healthz,
`proxy_pass ${PINGORA_UPSTREAM}/__genarrative_pingora/healthz;`,
'healthz canary 必须转到 Pingora 内部探针路径。',
);
for (const [header, block] of [
[websocketHeader, websocket],
[genericHeader, generic],
]) {
requireLocationIncludes(
header,
block,
`rewrite ^${CANARY_PREFIX}(/.*)$ $1 break;`,
'canary 前缀必须在转发给 Pingora 前剥离。',
);
requireLocationIncludes(
header,
block,
`proxy_pass ${PINGORA_UPSTREAM};`,
'剥离前缀后的请求必须交给 Pingora shadow。',
);
}
requireLocationIncludes(
websocketHeader,
websocket,
'proxy_set_header Upgrade $http_upgrade;',
'SpacetimeDB subscribe 必须保留 WebSocket Upgrade。',
);
requireLocationIncludes(
websocketHeader,
websocket,
'proxy_set_header Connection "Upgrade";',
'SpacetimeDB subscribe 必须保留 WebSocket Upgrade 连接语义。',
);
requireLocationIncludes(
websocketHeader,
websocket,
'proxy_read_timeout 3600s;',
'WebSocket canary 不能被短读超时提前断开。',
);
requireLocationIncludes(
genericHeader,
generic,
'proxy_buffering off;',
'API / SSE canary 必须保持低缓冲口径。',
);
requireLocationIncludes(
genericHeader,
generic,
'add_header X-Accel-Buffering no always;',
'API / SSE canary 必须显式关闭加速缓冲。',
);
requireLocationIncludes(
genericHeader,
generic,
'proxy_set_header X-Genarrative-Pingora-Canary "nginx-prefixed";',
'Pingora access log 需要能识别来自 Nginx 前缀 canary 的请求。',
);
if (
countOccurrences(
content,
'add_header X-Genarrative-Nginx-Handoff pingora-canary always;',
) !== 3
) {
fail(
`${activeSnippetPath}: handoff 响应头必须在 3 个 location 中各出现一次。`,
);
}
if (countOccurrences(content, 'deny all;') !== 3) {
fail(`${activeSnippetPath}: deny all 必须在 3 个 location 中各出现一次。`);
}
if (countOccurrences(content, 'X-Genarrative-Pingora-Probe') !== 1) {
fail(`${activeSnippetPath}: probe token 只能出现在 healthz canary 路由。`);
}
}
function runRealpathStaticChecks(content) {
requireIncludes(
content,
PROBE_TOKEN_PLACEHOLDER,
'仓库模板必须保留 probe token 占位符,部署时再替换为非 Git 密钥。',
);
requireIncludes(
content,
'server {',
'真实路径 canary 必须是独立 server 片段,避免 include 到生产 443 server 后覆盖正式 location。',
);
requireIncludes(
content,
'listen 127.0.0.1:18083;',
'真实路径 canary 只能默认监听本机 loopback 端口。',
);
requireIncludes(
content,
'access_log /var/log/nginx/genarrative-pingora-realpath-canary.access.log genarrative_upstream;',
'真实路径 canary 必须写独立 access log,避免和生产真实用户请求混淆。',
);
requireIncludes(
content,
`proxy_pass ${PINGORA_UPSTREAM}`,
'真实路径 canary 必须固定转发到本机 Pingora shadow 端口。',
);
requireNotMatches(
content,
/^\s*http\b/mu,
'真实路径 canary snippet 只能包含 server 片段,不能自行声明 http。',
);
requireNotMatches(
content,
/^\s*rewrite\b/mu,
'真实路径 canary 不允许 rewriteNginx 和 Pingora access log path 必须一一对齐。',
);
requireNotMatches(
content,
/^\s*listen\s+(?:80|443|0\.0\.0\.0|\[::\])/mu,
'真实路径 canary 默认不能监听公网地址或 80/443。',
);
const serverCount = [...content.matchAll(/^\s*server\s*\{/gmu)].length;
if (serverCount !== 1) {
fail(
`${activeSnippetPath}: 预期只有 1 个 server,实际 ${serverCount} 个。`,
);
}
for (const directive of ['allow 127.0.0.1;', 'allow ::1;', 'deny all;']) {
requireIncludes(
content,
directive,
'真实路径 canary 默认必须只放行本机,避免误 include 后公开接流。',
);
}
const locationBlocks = extractLocationBlocks(content);
if (locationBlocks.size !== 7) {
fail(
`${activeSnippetPath}: 真实路径 canary 预期只有 7 个 location,实际 ${locationBlocks.size} 个。`,
);
}
const healthzHeader = `location = ${REALPATH_HEALTHZ_PATH}`;
const apiHeader = 'location = /api/assets/history';
const websocketHeader = 'location ~ ^/v1/database/[^/]+/subscribe$';
const identityHeader = 'location ^~ /v1/identity';
const assetHeader = 'location = /assets/app.js';
const generatedHeader = 'location = /generated-pingora-canary-smoke';
const fallbackHeader = 'location /';
const healthz = requireLocation(locationBlocks, healthzHeader);
const api = requireLocation(locationBlocks, apiHeader);
const websocket = requireLocation(locationBlocks, websocketHeader);
const identity = requireLocation(locationBlocks, identityHeader);
const asset = requireLocation(locationBlocks, assetHeader);
const generated = requireLocation(locationBlocks, generatedHeader);
const fallback = requireLocation(locationBlocks, fallbackHeader);
for (const [header, block] of [
[healthzHeader, healthz],
[apiHeader, api],
[websocketHeader, websocket],
[identityHeader, identity],
[assetHeader, asset],
[generatedHeader, generated],
]) {
requireLocationIncludes(
header,
block,
REALPATH_HANDOFF,
'每条真实路径 canary 路由都必须可通过响应头审计 handoff。',
);
requireLocationIncludes(
header,
block,
'proxy_set_header X-Request-Id $request_id;',
'真实路径 canary 访问需要沿用 Nginx request_id,便于和 Pingora access log 对照。',
);
}
requireLocationIncludes(
healthzHeader,
healthz,
`proxy_set_header X-Genarrative-Pingora-Probe "${PROBE_TOKEN_PLACEHOLDER}";`,
'healthz handoff 必须带 shadow probe token。',
);
requireLocationIncludes(
healthzHeader,
healthz,
`proxy_pass ${PINGORA_UPSTREAM}/__genarrative_pingora/healthz;`,
'真实路径 canary healthz 必须转到 Pingora 内部探针路径。',
);
requireLocationIncludes(
apiHeader,
api,
'proxy_buffering off;',
'代表性 API 真实路径 canary 必须保持低缓冲口径。',
);
requireLocationIncludes(
apiHeader,
api,
'add_header X-Accel-Buffering no always;',
'代表性 API 真实路径 canary 必须显式关闭加速缓冲。',
);
requireLocationIncludes(
apiHeader,
api,
'proxy_set_header X-Genarrative-Pingora-Canary "nginx-realpath";',
'Pingora access log 需要能识别来自 Nginx 真实路径 canary 的请求。',
);
for (const [header, block] of [
[websocketHeader, websocket],
[identityHeader, identity],
]) {
requireLocationIncludes(
header,
block,
'proxy_set_header Upgrade $http_upgrade;',
'SpacetimeDB 真实路径 canary 必须保留 Upgrade 设置。',
);
requireLocationIncludes(
header,
block,
'proxy_set_header Connection "Upgrade";',
'SpacetimeDB 真实路径 canary 必须保留 Upgrade 连接语义。',
);
}
requireLocationIncludes(
websocketHeader,
websocket,
'proxy_read_timeout 3600s;',
'WebSocket 真实路径 canary 不能被短读超时提前断开。',
);
requireLocationIncludes(
fallbackHeader,
fallback,
'return 404;',
'真实路径 canary 只能覆盖代表路径,其余路径必须拒绝。',
);
if (countOccurrences(content, REALPATH_HANDOFF) !== 6) {
fail(
`${activeSnippetPath}: pingora-realpath-canary handoff 响应头必须在 6 个代理 location 中各出现一次。`,
);
}
if (countOccurrences(content, 'X-Genarrative-Pingora-Probe') !== 1) {
fail(
`${activeSnippetPath}: probe token 只能出现在真实路径 healthz canary 路由。`,
);
}
}
function hasNginxBinary() {
try {
execFileSync(nginxBinary, ['-v'], { encoding: 'utf8', stdio: 'pipe' });
return true;
} catch (error) {
if (explicitNginxBinary || requireNginx) {
fail(
`[check:nginx-pingora-canary] 无法执行 ${nginxBinary}: ${error.message}`,
);
}
return false;
}
}
function runNginxSyntaxCheck(prefixContent, realpathContent) {
if (!hasNginxBinary()) {
console.log(
'[check:nginx-pingora-canary] 未找到 nginx,已完成静态检查;需要强制 nginx -t 时使用 --require-nginx。',
);
return;
}
const tempRoot = mkdtempSync(
path.join(tmpdir(), 'genarrative-nginx-canary-'),
);
try {
const renderedPrefixSnippetPath = path.join(
tempRoot,
'genarrative-pingora-canary.conf',
);
const renderedRealpathSnippetPath = path.join(
tempRoot,
'genarrative-pingora-realpath-canary.conf',
);
const nginxConfigPath = path.join(tempRoot, 'nginx.conf');
const renderedPrefixSnippet = prefixContent.replaceAll(
PROBE_TOKEN_PLACEHOLDER,
'local-nginx-canary-check-token',
);
const renderedRealpathSnippet = realpathContent
.replaceAll(PROBE_TOKEN_PLACEHOLDER, 'local-nginx-canary-check-token')
.replace(
'/var/log/nginx/genarrative-pingora-realpath-canary.access.log',
path.join(tempRoot, 'genarrative-pingora-realpath-canary.access.log'),
)
.replace(
'/var/log/nginx/genarrative-pingora-realpath-canary.error.log',
path.join(tempRoot, 'genarrative-pingora-realpath-canary.error.log'),
);
const nginxConfig = `
pid ${tempRoot}/nginx.pid;
error_log stderr notice;
events {
worker_connections 64;
}
http {
log_format genarrative_upstream
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'request_time=$request_time upstream_connect_time=$upstream_connect_time '
'upstream_header_time=$upstream_header_time upstream_response_time=$upstream_response_time '
'upstream_status=$upstream_status request_id=$request_id';
access_log ${tempRoot}/access.log genarrative_upstream;
server {
listen 127.0.0.1:18082;
server_name genarrative-pingora-canary-check.local;
include ${renderedPrefixSnippetPath};
}
include ${renderedRealpathSnippetPath};
}
`;
writeFileSync(renderedPrefixSnippetPath, renderedPrefixSnippet, 'utf8');
writeFileSync(renderedRealpathSnippetPath, renderedRealpathSnippet, 'utf8');
writeFileSync(nginxConfigPath, nginxConfig, 'utf8');
execFileSync(
nginxBinary,
['-t', '-q', '-p', `${tempRoot}/`, '-c', nginxConfigPath],
{ encoding: 'utf8', stdio: 'pipe' },
);
console.log('[check:nginx-pingora-canary] nginx -t OK');
} catch (error) {
const stderr = error.stderr ? `\n${error.stderr}` : '';
const stdout = error.stdout ? `\n${error.stdout}` : '';
fail(`[check:nginx-pingora-canary] nginx -t 失败。${stderr}${stdout}`);
} finally {
rmSync(tempRoot, { force: true, recursive: true });
}
}
const prefixSnippet = readFileSync(PREFIX_SNIPPET_PATH, 'utf8');
activeSnippetPath = PREFIX_SNIPPET_PATH;
runStaticChecks(prefixSnippet);
const realpathSnippet = readFileSync(REALPATH_SNIPPET_PATH, 'utf8');
activeSnippetPath = REALPATH_SNIPPET_PATH;
runRealpathStaticChecks(realpathSnippet);
if (failures.length === 0) {
runNginxSyntaxCheck(prefixSnippet, realpathSnippet);
}
if (failures.length > 0) {
console.error('[check:nginx-pingora-canary] FAILED');
for (const failure of failures) {
console.error(`- ${failure}`);
}
process.exit(1);
}
console.log('[check:nginx-pingora-canary] OK');