#!/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 不允许 rewrite;Nginx 和 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');