fix: 收紧NapCatQQ自动化Jenkins入口

This commit is contained in:
sunlei 2026-06-25 03:46:22 +08:00
parent 4fc7c4a776
commit 36da962bb0
4 changed files with 117 additions and 8 deletions

View File

@ -57,8 +57,10 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
## Jenkins 入口模板
- `ci/jenkins/KT-NapCatQQ-Upstream-Sync.Jenkinsfile` 是 NapCatQQ 上游审计入口,只调用 ktWorkflow 的 `napcat-upstream-audit`,保留人工审查边界。
- `ci/jenkins/KT-NapCatQQ-Runtime-Release.Jenkinsfile` 是运行时发布入口,先调用 `napcat-runtime-release-readiness`,再通过显式参数触发运行时镜像构建和 API promotion。
- `ci/jenkins/KT-NapCatQQ-Upstream-Sync.Jenkinsfile` 是 NapCatQQ 上游审计入口,默认只调用 ktWorkflow 的 `napcat-upstream-audit` dry-run保留人工审查边界。
- Codex 上游审计必须同时设置 `RUN_CODEX_AUDIT=true` 并填写 `CONFIRM_CODEX_WORKSPACE_WRITE=RUN_CODEX_WORKSPACE_WRITE_AUDIT`;这是因为 Codex runner 使用 workspace-write sandbox不能作为定时任务默认行为。
- `ci/jenkins/KT-NapCatQQ-Runtime-Release.Jenkinsfile` 是运行时发布入口,先调用 `napcat-runtime-release-readiness`,再通过显式参数触发运行时镜像构建和 API promotion下游 job 使用白名单 choice避免任意 Jenkins job 名被传入。
- 生产 API promotion 除了 `PROMOTE_API_RUNTIME=true` 和明确 image/profile 参数外,还必须填写 `CONFIRM_API_PROMOTION_TARGET=PROMOTE_KT_TEMPLATE_API_MAIN`
- 两个模板默认使用 NAS 工作区 `/vol1/docker/kt-codex/workspace/KT`,运行证据落在 `/vol1/docker/kt-codex/artifacts` 下。
- 模板只保存入口和参数形状,不记录 Jenkins credential、Token、SSH key 或其他真实凭据。

View File

@ -13,9 +13,10 @@ pipeline {
string(name: 'QQBOT_NAPCAT_IMAGE_OVERRIDE', defaultValue: '', description: 'Explicit runtime image tag to build or promote, for example kt-napcat-desktop-cn:desktop-cn-v9.')
string(name: 'QQBOT_NAPCAT_DESKTOP_PROFILE_VERSION_OVERRIDE', defaultValue: '', description: 'Explicit API desktop profile version override.')
booleanParam(name: 'BUILD_RUNTIME_IMAGE', defaultValue: false, description: 'When true, trigger the reviewed Jenkins runtime image build/publish job.')
string(name: 'RUNTIME_BUILD_JOB', defaultValue: 'KT-NapCatQQ/Runtime-Image-Build', description: 'Jenkins job that performs the reviewed Docker runtime build and publish.')
choice(name: 'RUNTIME_BUILD_JOB', choices: ['KT-NapCatQQ/Runtime-Image-Build'], description: 'Whitelisted Jenkins job that performs the reviewed runtime image build and publish.')
booleanParam(name: 'PROMOTE_API_RUNTIME', defaultValue: false, description: 'When true, trigger API promotion with explicit image/profile overrides.')
string(name: 'API_PROMOTION_JOB', defaultValue: 'KT-Template/KT-Template-API/main', description: 'API Jenkins job that accepts NapCat runtime override parameters.')
choice(name: 'API_PROMOTION_JOB', choices: ['KT-Template/KT-Template-API/main'], description: 'Whitelisted API Jenkins job that accepts NapCat runtime override parameters.')
string(name: 'CONFIRM_API_PROMOTION_TARGET', defaultValue: '', description: 'Type PROMOTE_KT_TEMPLATE_API_MAIN before triggering the production API promotion job.')
}
environment {
@ -29,7 +30,7 @@ pipeline {
steps {
sh '''
set -e
pnpm --dir /vol1/docker/kt-codex/workspace/KT/mcp/ktWorkflow run napcat-runtime-release-readiness -- \
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-runtime-release-readiness -- \
--artifact-root "$KT_ARTIFACT_ROOT/napcat-runtime-release" \
--fork-branch "$NAPCAT_FORK_REF" \
--upstream-release-tag "$UPSTREAM_RELEASE_TAG" \
@ -45,6 +46,9 @@ pipeline {
}
steps {
script {
if (params.CONFIRM_API_PROMOTION_TARGET != 'PROMOTE_KT_TEMPLATE_API_MAIN') {
error('CONFIRM_API_PROMOTION_TARGET must equal PROMOTE_KT_TEMPLATE_API_MAIN before API promotion runs.')
}
requireRuntimeOverrides(
params.QQBOT_NAPCAT_IMAGE_OVERRIDE,
params.QQBOT_NAPCAT_DESKTOP_PROFILE_VERSION_OVERRIDE

View File

@ -7,15 +7,43 @@ pipeline {
timeout(time: 30, unit: 'MINUTES')
}
parameters {
booleanParam(name: 'RUN_CODEX_AUDIT', defaultValue: false, description: 'When true, allow the optional Codex workspace-write audit after the default dry-run audit.')
string(name: 'CONFIRM_CODEX_WORKSPACE_WRITE', defaultValue: '', description: 'Type RUN_CODEX_WORKSPACE_WRITE_AUDIT to confirm Codex may run with a workspace-write sandbox.')
}
environment {
KT_WORKFLOW_DIR = '/vol1/docker/kt-codex/workspace/KT/mcp/ktWorkflow'
KT_ARTIFACT_ROOT = '/vol1/docker/kt-codex/artifacts'
}
stages {
stage('ktWorkflow upstream audit') {
stage('ktWorkflow upstream audit dry-run') {
steps {
sh '''
set -e
pnpm --dir /vol1/docker/kt-codex/workspace/KT/mcp/ktWorkflow run napcat-upstream-audit -- \
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-upstream-audit -- \
--artifact-root "$KT_ARTIFACT_ROOT/napcat-upstream-sync/dry-run"
'''
}
}
stage('ktWorkflow upstream audit with Codex') {
when {
expression { return params.RUN_CODEX_AUDIT }
}
steps {
script {
if (params.CONFIRM_CODEX_WORKSPACE_WRITE != 'RUN_CODEX_WORKSPACE_WRITE_AUDIT') {
error('CONFIRM_CODEX_WORKSPACE_WRITE must equal RUN_CODEX_WORKSPACE_WRITE_AUDIT before Codex workspace-write audit runs.')
}
}
sh '''
set -e
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-upstream-audit -- \
--execute \
--use-codex \
--artifact-root /vol1/docker/kt-codex/artifacts/napcat-upstream-sync
--artifact-root "$KT_ARTIFACT_ROOT/napcat-upstream-sync/codex"
'''
}
}

View File

@ -449,6 +449,81 @@ export async function runSelfTest(): Promise<void> {
if (codexCommand.includes("git push") || codexCommand.includes("kubectl")) {
throw new Error("NapCat Codex runner mutation guard self-check failed");
}
const upstreamSyncJenkinsTemplate = readFileSync(
resolveInsideRoot(
"mcp/ktWorkflow/ci/jenkins/KT-NapCatQQ-Upstream-Sync.Jenkinsfile",
),
"utf8",
);
const runtimeReleaseJenkinsTemplate = readFileSync(
resolveInsideRoot(
"mcp/ktWorkflow/ci/jenkins/KT-NapCatQQ-Runtime-Release.Jenkinsfile",
),
"utf8",
);
for (const expected of [
"RUN_CODEX_AUDIT",
"CONFIRM_CODEX_WORKSPACE_WRITE",
"RUN_CODEX_WORKSPACE_WRITE_AUDIT",
]) {
if (!upstreamSyncJenkinsTemplate.includes(expected)) {
throw new Error(`NapCat upstream Jenkins template missing: ${expected}`);
}
}
const defaultUpstreamAuditIndex = upstreamSyncJenkinsTemplate.indexOf(
"run napcat-upstream-audit --",
);
const firstUpstreamExecuteIndex =
upstreamSyncJenkinsTemplate.indexOf("--execute");
if (
defaultUpstreamAuditIndex === -1 ||
(firstUpstreamExecuteIndex !== -1 &&
defaultUpstreamAuditIndex > firstUpstreamExecuteIndex)
) {
throw new Error(
"NapCat upstream Jenkins template must run dry-run audit before execute",
);
}
const defaultUpstreamAuditCall = upstreamSyncJenkinsTemplate.slice(
defaultUpstreamAuditIndex,
firstUpstreamExecuteIndex === -1
? upstreamSyncJenkinsTemplate.length
: firstUpstreamExecuteIndex,
);
if (defaultUpstreamAuditCall.includes("--use-codex")) {
throw new Error(
"NapCat upstream Jenkins template default audit must not use Codex",
);
}
for (const forbidden of ["git merge", "git push", "docker build", "kubectl"]) {
if (upstreamSyncJenkinsTemplate.includes(forbidden)) {
throw new Error(
`NapCat upstream Jenkins template forbidden command leaked: ${forbidden}`,
);
}
}
for (const expected of [
"choice(name: 'RUNTIME_BUILD_JOB'",
"choice(name: 'API_PROMOTION_JOB'",
"CONFIRM_API_PROMOTION_TARGET",
"PROMOTE_KT_TEMPLATE_API_MAIN",
]) {
if (!runtimeReleaseJenkinsTemplate.includes(expected)) {
throw new Error(`NapCat runtime Jenkins template missing: ${expected}`);
}
}
for (const forbidden of [
"string(name: 'RUNTIME_BUILD_JOB'",
"string(name: 'API_PROMOTION_JOB'",
"git merge",
"git push",
]) {
if (runtimeReleaseJenkinsTemplate.includes(forbidden)) {
throw new Error(
`NapCat runtime Jenkins template forbidden text leaked: ${forbidden}`,
);
}
}
const codexExecutionArtifactRoot = resolveInsideRoot(
".kt-workspace/test-artifacts/napcat-codex-runner-self-test",
);