#!/usr/bin/env node import { spawnSync } from 'node:child_process'; import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from 'node:fs'; import { tmpdir, userInfo } from 'node:os'; import path from 'node:path'; const ENABLE_SCRIPT = 'scripts/deploy/pingora-direct-enable.sh'; const PREFLIGHT_SCRIPT = 'scripts/check-pingora-direct-preflight.mjs'; const failures = []; const [DIRECT_TLS_PORT, DIRECT_HTTP_REDIRECT_PORT] = allocateLoopbackPorts(2); const DIRECT_TLS_LISTEN = `127.0.0.1:${DIRECT_TLS_PORT}`; const DIRECT_HTTP_REDIRECT_LISTEN = `127.0.0.1:${DIRECT_HTTP_REDIRECT_PORT}`; const tmpRoot = mkdtempSync(path.join(tmpdir(), 'genarrative-pingora-enable-')); try { main(); } finally { rmSync(tmpRoot, { recursive: true, force: true }); } if (failures.length > 0) { console.error('[check:pingora-direct-enable] FAILED'); for (const failure of failures) { console.error(`- ${failure}`); } process.exit(1); } console.log('[check:pingora-direct-enable] OK'); function main() { assertScriptShape(); assertDirectPreflightChecksServiceEnvironmentFile(); assertDirectPreflightRejectsPublicForwardedForTrust(); assertDryRunDoesNotInstallDropin(); assertDryRunRunsPreflightWhenRequested(); assertReleaseLayoutDryRunUsesBundledDirectChecks(); assertMissingTemplateDryRunWarns(); assertRelativePathsRejected(); assertFilesystemRootPathsRejected(); assertApplyRequiresPreflightEnvFile(); assertApplyRequiresCertReadablePreflight(); assertApplyRequiresServiceEnvFilePreflight(); assertApplyRequiresServiceUserCertReadablePreflight(); assertApplyRequiresServiceBinaryExecutablePreflight(); assertApplyRequiresPortsFreePreflight(); assertApplyRequiresDirectLiveArgs(); assertRejectsControlCharacterInputsBeforeApply(); assertApplyFailsWhenCurrentReleaseAuditFailsBeforeInstall(); assertApplyFailsWhenPreflightScriptMissingBeforeInstall(); assertApplyFailsWhenDirectLiveScriptMissingBeforeInstall(); assertApplyRejectsSymlinkDropinDirectoryBeforeInstall(); assertApplyRejectsSymlinkDropinFileBeforeInstall(); assertApplyFailsWhenSystemdEnvFileDiffers(); assertApplyFailsWhenSystemdEnvFileOnlySharesPrefix(); assertApplyFailsWhenSystemdExecStartDiffers(); assertApplyFailsWhenDirectLiveSmokeFails(); assertApplyFailsWhenDirectLiveAccessLogJsonMissing(); } function assertDirectPreflightChecksServiceEnvironmentFile() { const preflightRoot = path.join(tmpRoot, 'preflight-env-file'); const envPath = path.join(preflightRoot, 'pingora-gateway.env'); const servicePath = path.join(preflightRoot, 'genarrative-pingora-gateway.service'); const mismatchServicePath = path.join( preflightRoot, 'genarrative-pingora-gateway-mismatch.service', ); const dropinPath = path.join( preflightRoot, 'genarrative-pingora-gateway-direct-entry.conf', ); const envExamplePath = path.join(preflightRoot, 'pingora-gateway.env.example'); const systemctlBinary = path.join(preflightRoot, 'systemctl'); mkdirSync(preflightRoot, { recursive: true }); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( servicePath, [ '[Unit]', 'Description=Pingora 影子网关只监听本机高端口', '[Service]', `EnvironmentFile=-${envPath}`, 'ExecStart=/opt/genarrative/current/pingora-gateway', '', ].join('\n'), 'utf8', ); writeFileSync( mismatchServicePath, [ '[Unit]', 'Description=Pingora 影子网关只监听本机高端口', '[Service]', 'EnvironmentFile=/etc/genarrative/other-pingora.env', 'ExecStart=/opt/genarrative/current/pingora-gateway', '', ].join('\n'), 'utf8', ); writeFileSync( dropinPath, [ '# 人工复制到 /etc/systemd/system/genarrative-pingora-gateway.service.d/direct-entry.conf 后生效', '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync( envExamplePath, '# CAP_NET_BIND_SERVICE 只由 direct-entry drop-in 授予\n', 'utf8', ); const baseArgs = [ '--env-file', envPath, '--check-service-env-file', '--service-unit', servicePath, '--dropin-template', dropinPath, '--env-example', envExamplePath, ]; const okResult = runPreflight(baseArgs); assertStatus( okResult, 0, 'direct preflight 应接受 optional EnvironmentFile=- 指向本次 env。', ); const serviceMismatchResult = runPreflight([ '--env-file', envPath, '--check-service-env-file', '--service-unit', mismatchServicePath, '--dropin-template', dropinPath, '--env-example', envExamplePath, ]); if (serviceMismatchResult.status === 0) { failures.push('service 模板 EnvironmentFile 漂移时 direct preflight 必须失败。'); } assertIncludes( serviceMismatchResult.stderr, `${mismatchServicePath} EnvironmentFile 未包含本次 --env-file`, 'service 模板 EnvironmentFile 漂移时必须给出明确错误。', ); writeFileSync( systemctlBinary, [ '#!/usr/bin/env bash', 'if [[ "$1" == "cat" ]]; then', ' echo "[Service]"', ' echo "EnvironmentFile=/etc/genarrative/other-pingora.env"', ' echo "AmbientCapabilities=CAP_NET_BIND_SERVICE"', ' echo "CapabilityBoundingSet=CAP_NET_BIND_SERVICE"', ' exit 0', 'fi', 'exit 1', '', ].join('\n'), 'utf8', ); chmodExecutable(systemctlBinary); const systemdMismatchResult = runPreflight([...baseArgs, '--systemd-cat'], { PATH: `${preflightRoot}:${process.env.PATH || ''}`, }); if (systemdMismatchResult.status === 0) { failures.push('systemctl cat EnvironmentFile 漂移时 direct preflight 必须失败。'); } assertIncludes( systemdMismatchResult.stderr, 'systemctl cat genarrative-pingora-gateway.service EnvironmentFile 未包含本次 --env-file', 'systemd 最终配置 EnvironmentFile 漂移时必须给出明确错误。', ); } function assertDirectPreflightRejectsPublicForwardedForTrust() { const preflightRoot = path.join(tmpRoot, 'preflight-forwarded-for'); const envPath = path.join(preflightRoot, 'pingora-gateway.env'); const servicePath = path.join(preflightRoot, 'genarrative-pingora-gateway.service'); const dropinPath = path.join( preflightRoot, 'genarrative-pingora-gateway-direct-entry.conf', ); const envExamplePath = path.join(preflightRoot, 'pingora-gateway.env.example'); mkdirSync(preflightRoot, { recursive: true }); writeFileSync( servicePath, [ '[Unit]', 'Description=Pingora 影子网关只监听本机高端口', '[Service]', `EnvironmentFile=${envPath}`, 'ExecStart=/opt/genarrative/current/pingora-gateway', '', ].join('\n'), 'utf8', ); writeFileSync( dropinPath, [ '# 人工复制到 /etc/systemd/system/genarrative-pingora-gateway.service.d/direct-entry.conf 后生效', '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync( envExamplePath, '# CAP_NET_BIND_SERVICE 只由 direct-entry drop-in 授予\n', 'utf8', ); const baseArgs = [ '--env-file', envPath, '--require-live-env', '--service-unit', servicePath, '--dropin-template', dropinPath, '--env-example', envExamplePath, ]; writeFileSync( envPath, [ 'GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN=0.0.0.0:443', 'GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN=0.0.0.0:80', 'GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE=/tmp/cert.pem', 'GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE=/tmp/key.pem', 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https', 'GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR=true', 'GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED=true', '', ].join('\n'), 'utf8', ); const publicResult = runPreflight(baseArgs); if (publicResult.status === 0) { failures.push('公网直连开启 X-Forwarded-For 信任时 direct preflight 必须失败。'); } assertIncludes( publicResult.stderr, '公网直连 Pingora 时 GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR 必须保持 false', '公网直连误信任 X-Forwarded-For 时必须给出明确错误。', ); writeFileSync( envPath, [ `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN=${DIRECT_TLS_LISTEN}`, `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN=${DIRECT_HTTP_REDIRECT_LISTEN}`, 'GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE=/tmp/cert.pem', 'GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE=/tmp/key.pem', 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https', 'GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR=true', 'GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED=true', '', ].join('\n'), 'utf8', ); const loopbackResult = runPreflight(baseArgs); assertStatus( loopbackResult, 0, 'loopback 入口在显式确认前置代理清洗 X-Forwarded-For 后应允许 preflight 通过。', ); writeFileSync( envPath, [ `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN=${DIRECT_TLS_LISTEN}`, `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN=${DIRECT_HTTP_REDIRECT_LISTEN}`, 'GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE=/tmp/cert.pem', 'GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE=/tmp/key.pem', 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https', 'GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR=true', 'GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED=false', '', ].join('\n'), 'utf8', ); const missingConfirmationResult = runPreflight(baseArgs); if (missingConfirmationResult.status === 0) { failures.push('开启 X-Forwarded-For 信任但缺少确认开关时 direct preflight 必须失败。'); } assertIncludes( missingConfirmationResult.stderr, 'GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR=true 时必须同时设置 GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED=true', 'X-Forwarded-For 信任缺少确认开关时必须给出明确错误。', ); } function assertReleaseLayoutDryRunUsesBundledDirectChecks() { const currentUser = currentUsername(); const releaseRoot = path.join(tmpRoot, 'current-release'); const releaseScript = path.join( releaseRoot, 'scripts', 'deploy', 'pingora-direct-enable.sh', ); const templatePath = path.join( releaseRoot, 'deploy', 'systemd', 'genarrative-pingora-gateway-direct-entry.conf', ); const dropinPath = path.join( tmpRoot, 'release-layout-service.d', 'direct-entry.conf', ); const envPath = path.join(tmpRoot, 'release-layout-pingora-gateway.env'); const certPath = path.join(tmpRoot, 'release-layout-cert.pem'); const keyPath = path.join(tmpRoot, 'release-layout-key.pem'); const pingoraGatewayPath = path.join(releaseRoot, 'pingora-gateway'); mkdirSync(path.join(releaseRoot, 'scripts', 'deploy'), { recursive: true }); mkdirSync(path.join(releaseRoot, 'deploy', 'systemd'), { recursive: true }); mkdirSync(path.join(releaseRoot, 'deploy', 'pingora'), { recursive: true }); writeFileSync(releaseScript, readFileSync(ENABLE_SCRIPT, 'utf8'), 'utf8'); writeFileSync( path.join(releaseRoot, 'scripts', 'check-pingora-direct-preflight.mjs'), readFileSync('scripts/check-pingora-direct-preflight.mjs', 'utf8'), 'utf8', ); writeFileSync( path.join(releaseRoot, 'scripts', 'check-pingora-direct-live.mjs'), readFileSync('scripts/check-pingora-direct-live.mjs', 'utf8'), 'utf8', ); writeFileSync( path.join( releaseRoot, 'deploy', 'systemd', 'genarrative-pingora-gateway.service', ), `[Unit]\nDescription=Pingora 影子网关只监听本机高端口\n[Service]\nUser=${currentUser}\nEnvironmentFile=${envPath}\nExecStart=${pingoraGatewayPath}\n`, 'utf8', ); writeFileSync( templatePath, [ '# 人工复制到 /etc/systemd/system/genarrative-pingora-gateway.service.d/direct-entry.conf 后生效', '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync( path.join(releaseRoot, 'deploy', 'pingora', 'pingora-gateway.env.example'), [ '# CAP_NET_BIND_SERVICE 只由 direct-entry drop-in 授予', 'GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN=0.0.0.0:443', 'GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN=0.0.0.0:80', 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https', '', ].join('\n'), 'utf8', ); writeFileSync(certPath, 'fake-cert\n', 'utf8'); writeFileSync(keyPath, 'fake-key\n', 'utf8'); writeFileSync( envPath, [ `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN=${DIRECT_TLS_LISTEN}`, `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN=${DIRECT_HTTP_REDIRECT_LISTEN}`, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https', `GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE=${certPath}`, `GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE=${keyPath}`, '', ].join('\n'), 'utf8', ); writeFileSync( pingoraGatewayPath, '#!/usr/bin/env bash\nprintf "fake pingora gateway\\n"\n', 'utf8', ); chmodExecutable(releaseScript); chmodExecutable(pingoraGatewayPath); const result = spawnSync( 'bash', [ releaseScript, '--dropin-path', dropinPath, '--preflight-env-file', envPath, '--preflight-check-cert-readable', '--preflight-check-service-env-file', '--preflight-check-service-user-cert-readable', '--preflight-check-service-binary-executable', '--preflight-check-ports-free', '--no-status', ], { cwd: tmpRoot, encoding: 'utf8', env: process.env, }, ); assertStatus( result, 0, 'release current 目录中的 enable dry-run 应能使用随包携带的 direct preflight。', ); assertIncludes( result.stdout, `${releaseRoot}/scripts/check-pingora-direct-preflight.mjs`, 'release current 目录中的 enable dry-run 必须调用随包携带的 direct preflight。', ); assertIncludes( result.stdout, `template=${templatePath}`, 'release current 目录中的 enable dry-run 必须默认读取随包携带的 direct-entry 模板。', ); assertIncludes( result.stdout, `+ install -m 0644 ${templatePath} ${dropinPath}`, 'release current 目录中的 enable dry-run 必须展示安装随包 direct-entry 模板。', ); assertIncludes( result.stdout, '[pingora-direct-preflight] OK', 'release current 目录中的 direct preflight 必须带服务用户证书可读和 service 二进制可执行检查并真实通过。', ); if (existsSync(dropinPath)) { failures.push('release current 目录 dry-run 不应安装临时 drop-in 文件。'); } } function currentUsername() { if (process.env.USER || process.env.LOGNAME) { return process.env.USER || process.env.LOGNAME; } try { return userInfo().username; } catch { return 'root'; } } function assertScriptShape() { const content = readFileSync(ENABLE_SCRIPT, 'utf8'); assertIncludes(content, 'APPLY="false"', '启用脚本必须默认 dry-run。'); assertIncludes( content, '--apply', '启用脚本必须显式要求 --apply 才修改系统。', ); assertIncludes( content, 'systemctl daemon-reload', '安装 drop-in 后必须 reload systemd。', ); assertIncludes( content, 'restart "${SERVICE_NAME}"', '启用脚本必须重启 Pingora 服务。', ); assertIncludes( content, 'GENARRATIVE_PINGORA_DIRECT_TEMPLATE_PATH', '启用脚本必须支持覆盖 direct-entry 模板路径。', ); assertIncludes( content, 'deploy/systemd/genarrative-pingora-gateway-direct-entry.conf', '启用脚本默认模板必须来自 current release 随包 deploy/systemd。', ); assertIncludes( content, 'direct-entry.conf', '启用脚本必须默认指向 direct-entry drop-in。', ); assertIncludes( content, '该脚本不写入 TLS env', '启用脚本必须声明不改 TLS/env/证书边界。', ); assertIncludes( content, '--preflight-env-file', '启用脚本必须支持直连 env 预检参数。', ); assertIncludes( content, '--apply 必须同时提供 --preflight-env-file', '启用脚本 apply 时必须强制预检 env 文件。', ); assertIncludes( content, '--apply 必须同时提供 --preflight-check-cert-readable', '启用脚本 apply 时必须强制证书可读预检。', ); assertIncludes( content, '--apply 必须同时提供 --preflight-check-service-env-file', '启用脚本 apply 时必须强制 service EnvironmentFile 一致性预检。', ); assertIncludes( content, '--apply 必须同时提供 --preflight-check-service-user-cert-readable', '启用脚本 apply 时必须强制 systemd 服务用户证书可读预检。', ); assertIncludes( content, '--apply 必须同时提供 --preflight-check-service-binary-executable', '启用脚本 apply 时必须强制 Pingora service 二进制可执行预检。', ); assertIncludes( content, '--apply 必须同时提供 --preflight-check-ports-free', '启用脚本 apply 时必须强制端口释放预检。', ); assertIncludes( content, '--apply 必须同时提供 --direct-https-base-url', '启用脚本 apply 时必须强制 direct HTTPS live smoke 参数。', ); assertIncludes( content, '--apply 必须同时提供 --direct-http-base-url', '启用脚本 apply 时必须强制 direct HTTP live smoke 参数。', ); assertIncludes( content, '--apply 必须同时提供 --direct-host', '启用脚本 apply 时必须强制正式域名 Host/SNI 参数。', ); assertIncludes( content, '--direct-redirect-base-url', '启用脚本必须支持 direct live redirect base URL 覆盖,用于高端口 rehearsal。', ); assertIncludes( content, '--apply 必须同时提供 --direct-redirect-host', '启用脚本 apply 时必须强制 redirect Location host 参数。', ); assertIncludes( content, '--apply 必须同时提供 --direct-pingora-access-log', '启用脚本 apply 时必须强制 direct access log 落盘校验参数。', ); assertIncludes( content, '--apply 必须同时提供 --direct-spacetime-database', '启用脚本 apply 时必须强制 WSS subscribe 数据库名参数。', ); assertIncludes( content, 'GENARRATIVE_PINGORA_DIRECT_CURRENT_RELEASE_AUDIT_SCRIPT', '启用脚本必须支持固定 current release 自审脚本路径。', ); assertIncludes( content, '--current-release-root', '启用脚本必须支持固定 current release 根目录。', ); assertIncludes( content, '--require-pingora-gateway', '启用脚本 apply 前 current release 自审必须要求 Pingora 网关产物存在。', ); assertIncludes( content, '--systemd-show', '启用脚本 apply 前 current release 自审必须核验 systemd ExecStart。', ); assertIncludes( content, 'systemctl cat ${SERVICE_NAME}', '启用脚本必须在 apply 后核验 systemd 最终配置。', ); assertIncludes( content, 'systemctl is-active ${SERVICE_NAME}', '启用脚本必须在 apply 后核验 Pingora service active。', ); assertIncludes( content, 'systemctl show ${SERVICE_NAME} --property=ExecStart --value --no-pager', '启用脚本必须在 apply 后核验 systemd ExecStart 指向 current release。', ); assertIncludes( content, '主 service 模板中的', '启用脚本 ExecStart 核验必须以随包主 service 模板为真相源。', ); assertIncludes( content, 'AmbientCapabilities=CAP_NET_BIND_SERVICE', '启用脚本必须核验 AmbientCapabilities 已生效。', ); assertIncludes( content, 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '启用脚本必须核验 CapabilityBoundingSet 已生效。', ); assertIncludes( content, 'systemctl cat 未显示 EnvironmentFile=', '启用脚本必须在 apply 后核验 systemd 最终配置读取的 env 和 preflight env 一致。', ); assertIncludes( content, '--require-wss-upgrade', '启用脚本必须在 apply 后强制 direct live WSS 101。', ); assertIncludes( content, '--json', '启用脚本必须用 JSON 模式运行 direct live smoke,便于校验结构化接流证据。', ); assertIncludes( content, 'direct live JSON 缺少 direct-access-log 结构化结果', '启用脚本必须拒绝缺少 direct-access-log 结构化结果的 direct live 输出。', ); assertIncludes( content, 'direct-access-log evidence checked=', '启用脚本必须在 direct-access-log 结构化证据通过后输出确认摘要。', ); assertIncludes( content, '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 目录不能是符号链接', '启用脚本 apply 前必须拒绝符号链接 drop-in 目录。', ); assertIncludes( content, 'drop-in 目标不能是符号链接', '启用脚本 apply 前必须拒绝符号链接 drop-in 目标。', ); assertIncludes( content, 'reject_control_characters', '启用脚本必须统一拒绝换行或 NUL 参数。', ); assertIncludes( content, '不能包含换行或 NUL 字符', '启用脚本拒绝控制字符时必须给出明确错误。', ); assertIncludes( content, 'reject_filesystem_root_path', '启用脚本必须统一拒绝文件系统根目录路径参数。', ); assertIncludes( content, '不能是文件系统根目录', '启用脚本拒绝文件系统根目录路径时必须给出明确错误。', ); } function assertDryRunDoesNotInstallDropin() { const templatePath = path.join(tmpRoot, 'template.conf'); const dropinPath = path.join(tmpRoot, 'service.d', 'direct-entry.conf'); writeFileSync( templatePath, '[Service]\nAmbientCapabilities=CAP_NET_BIND_SERVICE\n', 'utf8', ); const result = runEnable([ '--template-path', templatePath, '--dropin-path', dropinPath, '--service', 'genarrative-pingora-gateway.service', '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-probe-token', 'direct-secret-token', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ]); assertStatus(result, 0, 'dry-run 应成功退出。'); assertIncludes( result.stdout, 'apply=false', 'dry-run 输出必须明确 apply=false。', ); assertIncludes( result.stdout, `+ install -d -m 0755 ${path.dirname(dropinPath)}`, 'dry-run 必须展示创建 drop-in 目录。', ); assertIncludes( result.stdout, `+ install -m 0644 ${templatePath} ${dropinPath}`, 'dry-run 必须展示安装 drop-in。', ); assertIncludes( result.stdout, '+ systemctl daemon-reload', 'dry-run 必须展示 daemon-reload。', ); assertIncludes( result.stdout, '+ systemctl restart genarrative-pingora-gateway.service', 'dry-run 必须展示重启 Pingora。', ); assertIncludes( result.stdout, '+ systemctl cat genarrative-pingora-gateway.service', 'dry-run 必须展示启用后 systemd drop-in 生效核验。', ); assertIncludes( result.stdout, '+ systemctl is-active genarrative-pingora-gateway.service', 'dry-run 必须展示启用后 service active 核验。', ); assertIncludes( result.stdout, '+ systemctl show genarrative-pingora-gateway.service --property=ExecStart --value --no-pager', 'dry-run 必须展示启用后 ExecStart 指向核验。', ); assertIncludes( result.stdout, '+ node ', 'dry-run 必须展示 current release 自审和启用后 direct live smoke。', ); assertIncludes( result.stdout, 'pingora-current-release-audit.mjs', 'dry-run 必须展示 apply 前 current release 自审。', ); assertIncludes( result.stdout, '--require-pingora-gateway --systemd-show', 'dry-run current release 自审必须要求 Pingora 产物和 systemd ExecStart。', ); assertIncludes( result.stdout, '--require-wss-upgrade', 'dry-run 必须展示 direct live smoke 强制 WSS 101。', ); assertIncludes( result.stdout, '--json', 'dry-run 必须展示 direct live smoke 会输出 JSON 结构化证据。', ); assertIncludes( result.stdout, '--pingora-access-log', 'dry-run 必须展示 direct live smoke 会校验 Pingora access log 落盘。', ); assertIncludes( result.stdout, '--probe-token ', 'dry-run direct live smoke 命令必须隐藏 probe token。', ); if (result.stdout.includes('direct-secret-token')) { failures.push('dry-run direct live smoke 命令不能泄露 probe token 原文。'); } if (existsSync(dropinPath)) { failures.push('dry-run 不应安装临时 drop-in 文件。'); } } function assertDryRunRunsPreflightWhenRequested() { const templatePath = path.join(tmpRoot, 'preflight-template.conf'); const dropinPath = path.join( tmpRoot, 'preflight-service.d', 'direct-entry.conf', ); const envPath = path.join(tmpRoot, 'pingora-gateway.env'); const certPath = path.join(tmpRoot, 'cert.pem'); const keyPath = path.join(tmpRoot, 'key.pem'); writeFileSync( templatePath, '[Service]\nAmbientCapabilities=CAP_NET_BIND_SERVICE\n', 'utf8', ); writeFileSync(certPath, 'fake-cert\n', 'utf8'); writeFileSync(keyPath, 'fake-key\n', 'utf8'); writeFileSync( envPath, [ `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN=${DIRECT_TLS_LISTEN}`, `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN=${DIRECT_HTTP_REDIRECT_LISTEN}`, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https', `GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE=${certPath}`, `GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE=${keyPath}`, '', ].join('\n'), 'utf8', ); const result = runEnable([ '--template-path', templatePath, '--dropin-path', dropinPath, '--preflight-env-file', envPath, '--preflight-check-cert-readable', '--preflight-check-ports-free', '--no-status', ]); assertStatus(result, 0, '带 preflight 的 dry-run 应成功退出。'); assertIncludes( result.stdout, '+ node ', 'dry-run 必须展示并执行 direct preflight。', ); assertIncludes( result.stdout, 'check-pingora-direct-preflight.mjs', 'dry-run preflight 命令必须调用 direct preflight 脚本。', ); assertIncludes( result.stdout, `--env-file ${envPath} --require-live-env --check-cert-readable --check-ports-free`, 'dry-run preflight 命令必须带 env、证书和端口释放检查参数。', ); assertIncludes( result.stdout, '[pingora-direct-preflight] OK', 'dry-run preflight 必须真实通过。', ); assertIncludes( result.stdout, '--https-base-url ', '带 preflight 的 dry-run 仍必须提示 apply 后会执行 direct live smoke。', ); if (existsSync(dropinPath)) { failures.push('带 preflight 的 dry-run 不应安装临时 drop-in 文件。'); } } function assertMissingTemplateDryRunWarns() { const templatePath = path.join(tmpRoot, 'missing-template.conf'); const dropinPath = path.join( tmpRoot, 'missing-service.d', 'direct-entry.conf', ); const result = runEnable([ '--template-path', templatePath, '--dropin-path', dropinPath, '--no-status', ]); assertStatus(result, 0, '模板不存在时 dry-run 应只提示不失败。'); assertIncludes( result.stderr, 'dry-run 提示:当前模板不存在', '模板不存在的 dry-run 必须提示 --apply 会失败。', ); } function assertRelativePathsRejected() { const templateResult = runEnable([ '--template-path', 'relative/template.conf', '--dropin-path', path.join(tmpRoot, 'service.d', 'direct-entry.conf'), '--no-status', ]); if (templateResult.status === 0) { failures.push('相对路径 template 必须被拒绝。'); } assertIncludes( templateResult.stderr, '--template-path 必须是绝对路径', '相对 template 路径负例必须说明需要绝对路径。', ); const dropinResult = runEnable([ '--template-path', path.join(tmpRoot, 'template.conf'), '--dropin-path', 'relative/direct-entry.conf', '--no-status', ]); if (dropinResult.status === 0) { failures.push('相对路径 drop-in 必须被拒绝。'); } assertIncludes( dropinResult.stderr, '--dropin-path 必须是绝对路径', '相对 drop-in 路径负例必须说明需要绝对路径。', ); const preflightResult = runEnable([ '--template-path', path.join(tmpRoot, 'template.conf'), '--dropin-path', path.join(tmpRoot, 'service.d', 'direct-entry.conf'), '--preflight-env-file', 'relative/pingora-gateway.env', '--no-status', ]); if (preflightResult.status === 0) { failures.push('相对路径 preflight env 必须被拒绝。'); } assertIncludes( preflightResult.stderr, '--preflight-env-file 必须是绝对路径', '相对 preflight env 路径负例必须说明需要绝对路径。', ); const auditScriptResult = runEnable([ '--template-path', path.join(tmpRoot, 'template.conf'), '--dropin-path', path.join(tmpRoot, 'service.d', 'direct-entry.conf'), '--current-release-audit-script', 'relative/pingora-current-release-audit.mjs', '--no-status', ]); if (auditScriptResult.status === 0) { failures.push('相对路径 current release 自审脚本必须被拒绝。'); } assertIncludes( auditScriptResult.stderr, '--current-release-audit-script 必须是绝对路径', '相对 current release 自审脚本路径负例必须说明需要绝对路径。', ); const preflightScriptResult = runEnable([ '--template-path', path.join(tmpRoot, 'template.conf'), '--dropin-path', path.join(tmpRoot, 'service.d', 'direct-entry.conf'), '--preflight-script', 'relative/check-pingora-direct-preflight.mjs', '--no-status', ]); if (preflightScriptResult.status === 0) { failures.push('相对路径 direct preflight 脚本必须被拒绝。'); } assertIncludes( preflightScriptResult.stderr, '--preflight-script 必须是绝对路径', '相对 direct preflight 脚本路径负例必须说明需要绝对路径。', ); const auditRootResult = runEnable([ '--template-path', path.join(tmpRoot, 'template.conf'), '--dropin-path', path.join(tmpRoot, 'service.d', 'direct-entry.conf'), '--current-release-root', 'relative/current', '--no-status', ]); if (auditRootResult.status === 0) { failures.push('相对路径 current release 根目录必须被拒绝。'); } assertIncludes( auditRootResult.stderr, '--current-release-root 必须是绝对路径', '相对 current release 根目录负例必须说明需要绝对路径。', ); const directLiveScriptResult = runEnable([ '--template-path', path.join(tmpRoot, 'template.conf'), '--dropin-path', path.join(tmpRoot, 'service.d', 'direct-entry.conf'), '--direct-live-script', 'relative/check-pingora-direct-live.mjs', '--no-status', ]); if (directLiveScriptResult.status === 0) { failures.push('相对路径 direct live smoke 脚本必须被拒绝。'); } assertIncludes( directLiveScriptResult.stderr, '--direct-live-script 必须是绝对路径', '相对 direct live smoke 脚本路径负例必须说明需要绝对路径。', ); } function assertFilesystemRootPathsRejected() { const rootDropinResult = runEnable([ '--template-path', path.join(tmpRoot, 'template.conf'), '--dropin-path', '/', '--no-status', ]); if (rootDropinResult.status === 0) { failures.push('文件系统根目录 --dropin-path 必须被拒绝。'); } assertIncludes( rootDropinResult.stderr, '--dropin-path 不能是文件系统根目录', '文件系统根目录 drop-in 路径负例必须给出明确错误。', ); const rootReleaseResult = runEnable([ '--template-path', path.join(tmpRoot, 'template.conf'), '--dropin-path', path.join(tmpRoot, 'service.d', 'direct-entry.conf'), '--current-release-root', '/', '--no-status', ]); if (rootReleaseResult.status === 0) { failures.push('文件系统根目录 --current-release-root 必须被拒绝。'); } assertIncludes( rootReleaseResult.stderr, '--current-release-root 不能是文件系统根目录', '文件系统根目录 current release 根目录负例必须给出明确错误。', ); } function assertApplyRequiresPreflightEnvFile() { const result = runEnable(['--apply', '--no-status']); if (result.status === 0) { failures.push('--apply 缺少 --preflight-env-file 必须被拒绝。'); } assertIncludes( result.stderr, '--apply 必须同时提供 --preflight-env-file', 'apply 负例必须说明不能跳过 preflight env。', ); } function assertApplyRequiresCertReadablePreflight() { const result = runEnable([ '--apply', '--preflight-env-file', path.join(tmpRoot, 'pingora-gateway.env'), '--preflight-check-service-env-file', '--preflight-check-service-user-cert-readable', '--preflight-check-ports-free', '--no-status', ]); if (result.status === 0) { failures.push('--apply 缺少 --preflight-check-cert-readable 必须被拒绝。'); } assertIncludes( result.stderr, '--apply 必须同时提供 --preflight-check-cert-readable', 'apply 负例必须说明不能跳过证书可读 preflight。', ); } function assertApplyRequiresServiceEnvFilePreflight() { const result = runEnable([ '--apply', '--preflight-env-file', path.join(tmpRoot, 'pingora-gateway.env'), '--preflight-check-cert-readable', '--preflight-check-service-user-cert-readable', '--preflight-check-ports-free', '--no-status', ]); if (result.status === 0) { failures.push( '--apply 缺少 --preflight-check-service-env-file 必须被拒绝。', ); } assertIncludes( result.stderr, '--apply 必须同时提供 --preflight-check-service-env-file', 'apply 负例必须说明不能跳过 service EnvironmentFile 一致性 preflight。', ); } function assertApplyRequiresServiceUserCertReadablePreflight() { const result = runEnable([ '--apply', '--preflight-env-file', path.join(tmpRoot, 'pingora-gateway.env'), '--preflight-check-cert-readable', '--preflight-check-service-env-file', '--preflight-check-ports-free', '--no-status', ]); if (result.status === 0) { failures.push( '--apply 缺少 --preflight-check-service-user-cert-readable 必须被拒绝。', ); } assertIncludes( result.stderr, '--apply 必须同时提供 --preflight-check-service-user-cert-readable', 'apply 负例必须说明不能跳过 systemd 服务用户证书可读 preflight。', ); } function assertApplyRequiresServiceBinaryExecutablePreflight() { const result = runEnable([ '--apply', '--preflight-env-file', path.join(tmpRoot, 'pingora-gateway.env'), '--preflight-check-cert-readable', '--preflight-check-service-env-file', '--preflight-check-service-user-cert-readable', '--preflight-check-ports-free', '--no-status', ]); if (result.status === 0) { failures.push( '--apply 缺少 --preflight-check-service-binary-executable 必须被拒绝。', ); } assertIncludes( result.stderr, '--apply 必须同时提供 --preflight-check-service-binary-executable', 'apply 负例必须说明不能跳过 service 二进制可执行 preflight。', ); } function assertApplyRequiresPortsFreePreflight() { const result = runEnable([ '--apply', '--preflight-env-file', path.join(tmpRoot, 'pingora-gateway.env'), '--preflight-check-cert-readable', '--preflight-check-service-env-file', '--preflight-check-service-user-cert-readable', '--preflight-check-service-binary-executable', '--no-status', ]); if (result.status === 0) { failures.push('--apply 缺少 --preflight-check-ports-free 必须被拒绝。'); } assertIncludes( result.stderr, '--apply 必须同时提供 --preflight-check-ports-free', 'apply 负例必须说明不能跳过端口释放 preflight。', ); } function assertApplyRequiresDirectLiveArgs() { const auditArgs = createFakeCurrentReleaseAuditArgs('direct-live-args-audit-ok'); const baseArgs = [ '--apply', '--preflight-env-file', path.join(tmpRoot, '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', ...auditArgs, '--no-status', ]; const httpsResult = runEnable(baseArgs); assertApplyFailure( httpsResult, '--apply 必须同时提供 --direct-https-base-url', '--apply 缺少 --direct-https-base-url 必须被拒绝。', ); const httpResult = runEnable([ ...baseArgs, '--direct-https-base-url', 'https://127.0.0.1', ]); assertApplyFailure( httpResult, '--apply 必须同时提供 --direct-http-base-url', '--apply 缺少 --direct-http-base-url 必须被拒绝。', ); const hostResult = runEnable([ ...baseArgs, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', ]); assertApplyFailure( hostResult, '--apply 必须同时提供 --direct-host', '--apply 缺少 --direct-host 必须被拒绝。', ); const redirectHostResult = runEnable([ ...baseArgs, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', ]); assertApplyFailure( redirectHostResult, '--apply 必须同时提供 --direct-redirect-host', '--apply 缺少 --direct-redirect-host 必须被拒绝。', ); const accessLogResult = runEnable([ ...baseArgs, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', ]); assertApplyFailure( accessLogResult, '--apply 必须同时提供 --direct-pingora-access-log', '--apply 缺少 --direct-pingora-access-log 必须被拒绝。', ); const databaseResult = runEnable([ ...baseArgs, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', ]); assertApplyFailure( databaseResult, '--apply 必须同时提供 --direct-spacetime-database', '--apply 缺少 --direct-spacetime-database 必须被拒绝。', ); } function assertRejectsControlCharacterInputsBeforeApply() { const fixture = createApplyPathSafetyFixture('control-character-input'); const dropinPath = path.join(fixture.root, 'service.d', 'direct-entry.conf'); const result = runEnable([ ...fixture.args, '--dropin-path', dropinPath, '--direct-host', 'example.com\ninjected.example.com', ]); if (result.status === 0) { failures.push('带换行的 direct host 必须在 apply 修改系统前被拒绝。'); } assertIncludes( result.stderr, '--direct-host 不能包含换行或 NUL 字符', 'direct host 控制字符负例必须给出明确错误。', ); if (existsSync(dropinPath)) { failures.push('带换行的 direct host 被拒绝后不应安装 direct-entry drop-in。'); } } function assertApplyFailsWhenCurrentReleaseAuditFailsBeforeInstall() { const templatePath = path.join(tmpRoot, 'audit-fail-template.conf'); const dropinPath = path.join(tmpRoot, 'audit-fail-service.d', 'direct-entry.conf'); const envPath = path.join(tmpRoot, 'audit-fail-pingora-gateway.env'); const auditScript = path.join(tmpRoot, 'fake-current-release-audit-fail.mjs'); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( auditScript, '#!/usr/bin/env node\nconsole.error("fake current release audit failed");\nprocess.exit(21);\n', 'utf8', ); chmodExecutable(auditScript); const result = runEnable([ '--apply', '--template-path', templatePath, '--dropin-path', dropinPath, '--current-release-audit-script', auditScript, '--current-release-root', tmpRoot, '--preflight-env-file', envPath, '--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', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ]); if (result.status === 0) { failures.push('current release 自审失败时 enable apply 必须失败。'); } assertIncludes( result.stderr, 'fake current release audit failed', 'current release 自审失败时必须暴露自审错误。', ); if (existsSync(dropinPath)) { failures.push('current release 自审失败发生在安装前,不应留下 direct-entry drop-in。'); } } function assertApplyFailsWhenPreflightScriptMissingBeforeInstall() { const templatePath = path.join(tmpRoot, 'preflight-missing-template.conf'); const dropinPath = path.join( tmpRoot, 'preflight-missing-service.d', 'direct-entry.conf', ); const envPath = path.join(tmpRoot, 'preflight-missing-pingora-gateway.env'); const missingPreflightScript = path.join( tmpRoot, 'missing-check-pingora-direct-preflight.mjs', ); const directLiveScript = path.join(tmpRoot, 'fake-direct-live-preflight-missing.mjs'); const auditArgs = createFakeCurrentReleaseAuditArgs('preflight-missing-audit-ok'); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( directLiveScript, '#!/usr/bin/env node\nconsole.log("[fake-direct-live] OK");\n', 'utf8', ); chmodExecutable(directLiveScript); const result = runEnable([ '--apply', '--template-path', templatePath, '--dropin-path', dropinPath, ...auditArgs, '--preflight-script', missingPreflightScript, '--preflight-env-file', envPath, '--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-live-script', directLiveScript, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ]); if (result.status === 0) { failures.push('direct preflight 脚本缺失时 enable apply 必须失败。'); } assertIncludes( result.stderr, 'direct preflight 脚本不存在', 'direct preflight 脚本缺失时必须给出明确错误。', ); if (existsSync(dropinPath)) { failures.push('direct preflight 脚本缺失发生在安装前,不应留下 direct-entry drop-in。'); } } function assertApplyFailsWhenDirectLiveScriptMissingBeforeInstall() { const templatePath = path.join(tmpRoot, 'direct-live-missing-template.conf'); const dropinPath = path.join( tmpRoot, 'direct-live-missing-service.d', 'direct-entry.conf', ); const envPath = path.join(tmpRoot, 'direct-live-missing-pingora-gateway.env'); const preflightScript = path.join(tmpRoot, 'fake-preflight-direct-live-missing.mjs'); const missingDirectLiveScript = path.join( tmpRoot, 'missing-check-pingora-direct-live.mjs', ); const auditArgs = createFakeCurrentReleaseAuditArgs('direct-live-missing-audit-ok'); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( preflightScript, '#!/usr/bin/env node\nconsole.log("[fake-preflight] OK");\n', 'utf8', ); chmodExecutable(preflightScript); const result = runEnable([ '--apply', '--template-path', templatePath, '--dropin-path', dropinPath, ...auditArgs, '--preflight-script', preflightScript, '--preflight-env-file', envPath, '--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-live-script', missingDirectLiveScript, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ]); if (result.status === 0) { failures.push('direct live smoke 脚本缺失时 enable apply 必须失败。'); } assertIncludes( result.stderr, 'direct live smoke 脚本不存在', 'direct live smoke 脚本缺失时必须给出明确错误。', ); if (existsSync(dropinPath)) { failures.push('direct live smoke 脚本缺失发生在安装前,不应留下 direct-entry drop-in。'); } } function assertApplyRejectsSymlinkDropinDirectoryBeforeInstall() { const fixture = createApplyPathSafetyFixture('symlink-dropin-dir'); const realDropinDir = path.join(fixture.root, 'real-service.d'); const symlinkDropinDir = path.join(fixture.root, 'linked-service.d'); mkdirSync(realDropinDir, { recursive: true }); symlinkSync(realDropinDir, symlinkDropinDir); const dropinPath = path.join(symlinkDropinDir, 'direct-entry.conf'); const result = runEnable([...fixture.args, '--dropin-path', dropinPath]); if (result.status === 0) { failures.push('drop-in 目录是符号链接时 enable apply 必须失败。'); } assertIncludes( result.stderr, 'drop-in 目录不能是符号链接', 'drop-in 符号链接目录负例必须给出明确错误。', ); if (existsSync(path.join(realDropinDir, 'direct-entry.conf'))) { failures.push('drop-in 目录是符号链接时不应写入真实目标目录。'); } } function assertApplyRejectsSymlinkDropinFileBeforeInstall() { const fixture = createApplyPathSafetyFixture('symlink-dropin-file'); const dropinDir = path.join(fixture.root, 'service.d'); const realDropinFile = path.join(fixture.root, 'real-direct-entry.conf'); const symlinkDropinFile = path.join(dropinDir, 'direct-entry.conf'); mkdirSync(dropinDir, { recursive: true }); writeFileSync(realDropinFile, 'original dropin\n', 'utf8'); symlinkSync(realDropinFile, symlinkDropinFile); const result = runEnable([...fixture.args, '--dropin-path', symlinkDropinFile]); if (result.status === 0) { failures.push('drop-in 目标是符号链接时 enable apply 必须失败。'); } assertIncludes( result.stderr, 'drop-in 目标不能是符号链接', 'drop-in 符号链接目标负例必须给出明确错误。', ); const realContent = readFileSync(realDropinFile, 'utf8'); if (realContent !== 'original dropin\n') { failures.push('drop-in 目标是符号链接时不应改写真实目标文件。'); } } function assertApplyFailsWhenSystemdEnvFileDiffers() { const templatePath = path.join(tmpRoot, 'systemd-env-mismatch-template.conf'); const dropinPath = path.join( tmpRoot, 'systemd-env-mismatch-service.d', 'direct-entry.conf', ); const envPath = path.join(tmpRoot, 'systemd-env-mismatch-pingora-gateway.env'); const preflightScript = path.join(tmpRoot, 'fake-preflight-env-ok.mjs'); const directLiveScript = path.join(tmpRoot, 'fake-direct-live-env-ok.mjs'); const auditArgs = createFakeCurrentReleaseAuditArgs('systemd-env-mismatch-audit-ok'); const systemctlBinary = path.join(tmpRoot, 'systemctl'); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( preflightScript, '#!/usr/bin/env node\nconsole.log("[fake-preflight] OK");\n', 'utf8', ); writeFileSync( directLiveScript, '#!/usr/bin/env node\nconsole.log("[fake-direct-live] OK");\n', 'utf8', ); writeFileSync( systemctlBinary, [ '#!/usr/bin/env bash', 'if [[ "$1" == "cat" ]]; then', ' echo "[Service]"', ' echo "EnvironmentFile=/etc/genarrative/other-pingora.env"', ' echo "AmbientCapabilities=CAP_NET_BIND_SERVICE"', ' echo "CapabilityBoundingSet=CAP_NET_BIND_SERVICE"', ' exit 0', 'fi', 'if [[ "$1" == "is-active" ]]; then', ' echo active', ' exit 0', 'fi', 'exit 0', '', ].join('\n'), 'utf8', ); chmodExecutable(preflightScript); chmodExecutable(directLiveScript); chmodExecutable(systemctlBinary); const result = runEnable( [ '--apply', '--template-path', templatePath, '--dropin-path', dropinPath, ...auditArgs, '--preflight-script', preflightScript, '--preflight-env-file', envPath, '--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-live-script', directLiveScript, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ], { PATH: `${tmpRoot}:${process.env.PATH || ''}` }, ); if (result.status === 0) { failures.push('systemctl cat 的 EnvironmentFile 漂移时 enable apply 必须失败。'); } assertIncludes( result.stderr, `systemctl cat 未显示 EnvironmentFile=${envPath}`, 'systemd 最终配置读取的 env 和 preflight env 不一致时必须给出明确错误。', ); } function assertApplyFailsWhenSystemdEnvFileOnlySharesPrefix() { const templatePath = path.join(tmpRoot, 'systemd-env-prefix-template.conf'); const dropinPath = path.join( tmpRoot, 'systemd-env-prefix-service.d', 'direct-entry.conf', ); const envPath = path.join(tmpRoot, 'systemd-env-prefix-pingora-gateway.env'); const preflightScript = path.join(tmpRoot, 'fake-preflight-prefix-ok.mjs'); const directLiveScript = path.join(tmpRoot, 'fake-direct-live-prefix-ok.mjs'); const auditArgs = createFakeCurrentReleaseAuditArgs('systemd-env-prefix-audit-ok'); const systemctlBinary = path.join(tmpRoot, 'systemctl'); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( preflightScript, '#!/usr/bin/env node\nconsole.log("[fake-preflight] OK");\n', 'utf8', ); writeFileSync( directLiveScript, '#!/usr/bin/env node\nconsole.log("[fake-direct-live] OK");\n', 'utf8', ); writeFileSync( systemctlBinary, [ '#!/usr/bin/env bash', 'if [[ "$1" == "cat" ]]; then', ' echo "[Service]"', ` echo "EnvironmentFile=${envPath}.bak"`, ' echo "AmbientCapabilities=CAP_NET_BIND_SERVICE"', ' echo "CapabilityBoundingSet=CAP_NET_BIND_SERVICE"', ' exit 0', 'fi', 'if [[ "$1" == "is-active" ]]; then', ' echo active', ' exit 0', 'fi', 'exit 0', '', ].join('\n'), 'utf8', ); chmodExecutable(preflightScript); chmodExecutable(directLiveScript); chmodExecutable(systemctlBinary); const result = runEnable( [ '--apply', '--template-path', templatePath, '--dropin-path', dropinPath, ...auditArgs, '--preflight-script', preflightScript, '--preflight-env-file', envPath, '--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-live-script', directLiveScript, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ], { PATH: `${tmpRoot}:${process.env.PATH || ''}` }, ); if (result.status === 0) { failures.push( 'systemctl cat 的 EnvironmentFile 只共享路径前缀时 enable apply 必须失败。', ); } assertIncludes( result.stderr, `systemctl cat 未显示 EnvironmentFile=${envPath}`, 'systemd 最终配置 EnvironmentFile 不能用路径前缀误判为一致。', ); } function assertApplyFailsWhenDirectLiveSmokeFails() { const templatePath = path.join(tmpRoot, 'direct-live-fail-template.conf'); const serviceUnitPath = path.join(tmpRoot, 'direct-live-fail-service.service'); const dropinPath = path.join( tmpRoot, 'direct-live-fail-service.d', 'direct-entry.conf', ); const envPath = path.join(tmpRoot, 'direct-live-fail-pingora-gateway.env'); const certPath = path.join(tmpRoot, 'direct-live-fail-cert.pem'); const keyPath = path.join(tmpRoot, 'direct-live-fail-key.pem'); const preflightScript = path.join(tmpRoot, 'fake-preflight-ok.mjs'); const directLiveScript = path.join(tmpRoot, 'fake-direct-live-fail.mjs'); const auditArgs = createFakeCurrentReleaseAuditArgs('direct-live-fail-audit-ok'); const systemctlBinary = path.join(tmpRoot, 'systemctl'); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync( serviceUnitPath, [ '[Unit]', 'Description=Pingora 影子网关只监听本机高端口', '[Service]', `EnvironmentFile=${envPath}`, 'ExecStart=/opt/genarrative/current/pingora-gateway', '', ].join('\n'), 'utf8', ); writeFileSync(certPath, 'fake-cert\n', 'utf8'); writeFileSync(keyPath, 'fake-key\n', 'utf8'); writeFileSync( envPath, [ `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN=${DIRECT_TLS_LISTEN}`, `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN=${DIRECT_HTTP_REDIRECT_LISTEN}`, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https', `GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE=${certPath}`, `GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE=${keyPath}`, '', ].join('\n'), 'utf8', ); writeFileSync( preflightScript, '#!/usr/bin/env node\nconsole.log("[fake-preflight] OK");\n', 'utf8', ); writeFileSync( directLiveScript, '#!/usr/bin/env node\nconsole.error("fake direct live failed");\nprocess.exit(12);\n', 'utf8', ); writeFileSync( systemctlBinary, [ '#!/usr/bin/env bash', 'if [[ "$1" == "cat" ]]; then', ' echo "[Service]"', ` echo "EnvironmentFile=${envPath}"`, ' echo "AmbientCapabilities=CAP_NET_BIND_SERVICE"', ' echo "CapabilityBoundingSet=CAP_NET_BIND_SERVICE"', ' exit 0', 'fi', 'if [[ "$1" == "show" ]]; then', ' echo "{ path=/opt/genarrative/current/pingora-gateway ; argv[]=/opt/genarrative/current/pingora-gateway ; }"', ' exit 0', 'fi', 'if [[ "$1" == "is-active" ]]; then', ' echo active', ' exit 0', 'fi', 'exit 0', '', ].join('\n'), 'utf8', ); chmodExecutable(preflightScript); chmodExecutable(directLiveScript); chmodExecutable(systemctlBinary); const result = runEnable( [ '--apply', '--service-unit-path', serviceUnitPath, '--template-path', templatePath, '--dropin-path', dropinPath, ...auditArgs, '--preflight-script', preflightScript, '--preflight-env-file', envPath, '--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-live-script', directLiveScript, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ], { PATH: `${tmpRoot}:${process.env.PATH || ''}` }, ); if (result.status === 0) { failures.push('direct live smoke 失败时 enable apply 必须失败。'); } assertIncludes( result.stderr, 'fake direct live failed', 'direct live smoke 失败时必须暴露检查错误。', ); if (!existsSync(dropinPath)) { failures.push( 'direct live smoke 失败发生在安装后,临时 drop-in 应已存在供后续回退脚本处理。', ); } } function assertApplyFailsWhenDirectLiveAccessLogJsonMissing() { const templatePath = path.join(tmpRoot, 'direct-live-json-missing-template.conf'); const serviceUnitPath = path.join( tmpRoot, 'direct-live-json-missing-service.service', ); const dropinPath = path.join( tmpRoot, 'direct-live-json-missing-service.d', 'direct-entry.conf', ); const envPath = path.join( tmpRoot, 'direct-live-json-missing-pingora-gateway.env', ); const preflightScript = path.join( tmpRoot, 'fake-preflight-json-missing-ok.mjs', ); const directLiveScript = path.join( tmpRoot, 'fake-direct-live-json-missing.mjs', ); const auditArgs = createFakeCurrentReleaseAuditArgs( 'direct-live-json-missing-audit-ok', ); const systemctlBinary = path.join(tmpRoot, 'systemctl'); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync( serviceUnitPath, [ '[Unit]', 'Description=Pingora 影子网关只监听本机高端口', '[Service]', `EnvironmentFile=${envPath}`, 'ExecStart=/opt/genarrative/current/pingora-gateway', '', ].join('\n'), 'utf8', ); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( preflightScript, '#!/usr/bin/env node\nconsole.log("[fake-preflight] OK");\n', 'utf8', ); writeFileSync( directLiveScript, [ '#!/usr/bin/env node', 'console.log("[fake-direct-live] OK");', 'console.log(JSON.stringify({ ok: true, results: [{ name: "https-root" }] }, null, 2));', '', ].join('\n'), 'utf8', ); writeFileSync( systemctlBinary, [ '#!/usr/bin/env bash', 'if [[ "$1" == "cat" ]]; then', ' echo "[Service]"', ` echo "EnvironmentFile=${envPath}"`, ' echo "AmbientCapabilities=CAP_NET_BIND_SERVICE"', ' echo "CapabilityBoundingSet=CAP_NET_BIND_SERVICE"', ' exit 0', 'fi', 'if [[ "$1" == "show" ]]; then', ' echo "{ path=/opt/genarrative/current/pingora-gateway ; argv[]=/opt/genarrative/current/pingora-gateway ; }"', ' exit 0', 'fi', 'if [[ "$1" == "is-active" ]]; then', ' echo active', ' exit 0', 'fi', 'exit 0', '', ].join('\n'), 'utf8', ); chmodExecutable(preflightScript); chmodExecutable(directLiveScript); chmodExecutable(systemctlBinary); const result = runEnable( [ '--apply', '--service-unit-path', serviceUnitPath, '--template-path', templatePath, '--dropin-path', dropinPath, ...auditArgs, '--preflight-script', preflightScript, '--preflight-env-file', envPath, '--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-live-script', directLiveScript, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ], { PATH: `${tmpRoot}:${process.env.PATH || ''}` }, ); if (result.status === 0) { failures.push( 'direct live JSON 缺少 direct-access-log 时 enable apply 必须失败。', ); } assertIncludes( result.stderr, 'direct live JSON 缺少 direct-access-log 结构化结果', 'direct live JSON 缺少 access log 结构化结果时必须给出明确错误。', ); if (!existsSync(dropinPath)) { failures.push( 'direct live JSON 结构化证据失败发生在安装后,临时 drop-in 应已存在供后续回退脚本处理。', ); } } function assertApplyFailsWhenSystemdExecStartDiffers() { const templatePath = path.join(tmpRoot, 'systemd-exec-mismatch-template.conf'); const serviceUnitPath = path.join(tmpRoot, 'systemd-exec-mismatch.service'); const dropinPath = path.join( tmpRoot, 'systemd-exec-mismatch-service.d', 'direct-entry.conf', ); const envPath = path.join(tmpRoot, 'systemd-exec-mismatch-pingora-gateway.env'); const preflightScript = path.join(tmpRoot, 'fake-preflight-exec-ok.mjs'); const directLiveScript = path.join(tmpRoot, 'fake-direct-live-exec-ok.mjs'); const auditArgs = createFakeCurrentReleaseAuditArgs('systemd-exec-mismatch-audit-ok'); const systemctlBinary = path.join(tmpRoot, 'systemctl'); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync( serviceUnitPath, [ '[Unit]', 'Description=Pingora 影子网关只监听本机高端口', '[Service]', `EnvironmentFile=${envPath}`, 'ExecStart=/opt/genarrative/current/pingora-gateway', '', ].join('\n'), 'utf8', ); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( preflightScript, '#!/usr/bin/env node\nconsole.log("[fake-preflight] OK");\n', 'utf8', ); writeFileSync( directLiveScript, '#!/usr/bin/env node\nconsole.log("[fake-direct-live] OK");\n', 'utf8', ); writeFileSync( systemctlBinary, [ '#!/usr/bin/env bash', 'if [[ "$1" == "cat" ]]; then', ' echo "[Service]"', ` echo "EnvironmentFile=${envPath}"`, ' echo "AmbientCapabilities=CAP_NET_BIND_SERVICE"', ' echo "CapabilityBoundingSet=CAP_NET_BIND_SERVICE"', ' exit 0', 'fi', 'if [[ "$1" == "show" ]]; then', ' echo "{ path=/opt/genarrative/old-release/pingora-gateway ; argv[]=/opt/genarrative/old-release/pingora-gateway ; }"', ' exit 0', 'fi', 'if [[ "$1" == "is-active" ]]; then', ' echo active', ' exit 0', 'fi', 'exit 0', '', ].join('\n'), 'utf8', ); chmodExecutable(preflightScript); chmodExecutable(directLiveScript); chmodExecutable(systemctlBinary); const result = runEnable( [ '--apply', '--service-unit-path', serviceUnitPath, '--template-path', templatePath, '--dropin-path', dropinPath, ...auditArgs, '--preflight-script', preflightScript, '--preflight-env-file', envPath, '--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-live-script', directLiveScript, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ], { PATH: `${tmpRoot}:${process.env.PATH || ''}` }, ); if (result.status === 0) { failures.push('systemctl show ExecStart 指向旧 release 时 enable apply 必须失败。'); } assertIncludes( result.stderr, 'systemctl show ExecStart 未指向主 service 模板中的 /opt/genarrative/current/pingora-gateway', 'systemd 最终 ExecStart 指向旧 release 时必须给出明确错误。', ); } function runEnable(args, env = {}) { return spawnSync('bash', [ENABLE_SCRIPT, ...args], { cwd: process.cwd(), encoding: 'utf8', env: { ...process.env, ...env, }, }); } function runPreflight(args, env = {}) { return spawnSync(process.execPath, ['--', PREFLIGHT_SCRIPT, ...args], { cwd: process.cwd(), encoding: 'utf8', env: { ...process.env, ...env, }, }); } function chmodExecutable(filePath) { spawnSync('chmod', ['0755', filePath], { cwd: process.cwd(), encoding: 'utf8', }); } function createFakeCurrentReleaseAuditArgs(name) { const auditScript = path.join(tmpRoot, `${name}.mjs`); writeFileSync( auditScript, '#!/usr/bin/env node\nconsole.log("[fake-current-release-audit] OK");\n', 'utf8', ); chmodExecutable(auditScript); return ['--current-release-audit-script', auditScript, '--current-release-root', tmpRoot]; } function createApplyPathSafetyFixture(name) { const root = path.join(tmpRoot, name); mkdirSync(root, { recursive: true }); const templatePath = path.join(root, 'template.conf'); const envPath = path.join(root, 'pingora-gateway.env'); const preflightScript = path.join(root, 'fake-preflight.mjs'); const directLiveScript = path.join(root, 'fake-direct-live.mjs'); const auditArgs = createFakeCurrentReleaseAuditArgs(`${name}-audit-ok`); writeFileSync( templatePath, [ '[Service]', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'CapabilityBoundingSet=CAP_NET_BIND_SERVICE', '', ].join('\n'), 'utf8', ); writeFileSync(envPath, 'GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO=https\n', 'utf8'); writeFileSync( preflightScript, '#!/usr/bin/env node\nconsole.log("[fake-preflight] OK");\n', 'utf8', ); writeFileSync( directLiveScript, '#!/usr/bin/env node\nconsole.log("[fake-direct-live] OK");\n', 'utf8', ); chmodExecutable(preflightScript); chmodExecutable(directLiveScript); return { root, args: [ '--apply', '--template-path', templatePath, ...auditArgs, '--preflight-script', preflightScript, '--preflight-env-file', envPath, '--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-live-script', directLiveScript, '--direct-https-base-url', 'https://127.0.0.1', '--direct-http-base-url', 'http://127.0.0.1', '--direct-host', 'example.com', '--direct-redirect-host', 'example.com', '--direct-pingora-access-log', '/var/log/genarrative/pingora-gateway.access.log', '--direct-spacetime-database', 'genarrative-prod', '--no-status', ], }; } function assertApplyFailure(result, needle, reason) { if (result.status === 0) { failures.push(reason); return; } assertIncludes(result.stderr, needle, `${reason} 错误信息应明确。`); } function assertStatus(result, expected, reason) { const actual = result.status ?? 0; if (actual !== expected) { failures.push( `${reason} 预期退出码 ${expected},实际 ${actual}。\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`, ); } } function assertIncludes(content, needle, reason) { if (!content.includes(needle)) { failures.push(`${reason} 缺少: ${needle}`); } } function allocateLoopbackPorts(count) { const ports = []; for (let offset = 0; ports.length < count && offset < 1000; offset += 1) { const candidate = 28080 + offset; const result = spawnSync( process.execPath, [ '-e', [ "const net = require('node:net');", `const server = net.createServer();`, `server.once('error', () => process.exit(1));`, `server.listen(${candidate}, '127.0.0.1', () => server.close(() => process.exit(0)));`, ].join(''), ], { cwd: process.cwd(), encoding: 'utf8', }, ); if ((result.status ?? 1) === 0) { ports.push(candidate); } } if (ports.length !== count) { throw new Error('无法为 Pingora direct enable 自测分配空闲 loopback 端口。'); } return ports; }