diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 35d2291aa..d8a849700 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -158,9 +158,9 @@ ## 2026-06-19 Jenkins Git 源统一为 genarrative-station - 背景:本地和 Jenkins 流水线改用 Gitea 的 `/git` 前缀内网入口后,继续在 Jenkinsfile 内保留 `http://127.0.0.1:3000/...` 主地址和 `https://git.genarrative.world/...` fallback 会让构建节点误走 localhost 或公网链路。 -- 决策:常规生产构建、数据库导入导出和 `Genarrative-Full-Build-And-Deploy` 的 Jenkinsfile 内部 checkout 统一使用 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`;`GIT_REMOTE_FALLBACK_URL` 留空,主地址失败时直接失败,不再回退公网域名。`Genarrative-Server-Provision` 继续要求参数显式填写目标 agent 可访问的本机路径或内网 Git 源。 +- 决策:常规生产构建、数据库导入导出和 `Genarrative-Full-Build-And-Deploy` 的 Jenkinsfile 内部 checkout 统一使用 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`,并显式传入 Jenkins HTTP 凭据 `genarrative-local-gitea-http`;HTTP 凭据缺失或鉴权失败时允许回退到 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git`,使用现有 SSH 凭据 `genarrative-local-gitea-ssh`。该 fallback 只用于内网 Gitea 鉴权兜底,不再回退公网域名。`Genarrative-Server-Provision` 继续要求参数显式填写目标 agent 可访问的本机路径或内网 Git 源。 - 影响范围:`jenkins/Jenkinsfile.production-api-build`、`jenkins/Jenkinsfile.production-web-build`、`jenkins/Jenkinsfile.production-stdb-module-build`、`jenkins/Jenkinsfile.production-full-build-and-deploy`、`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`、生产 Jenkins live job SCM 配置和运维文档。 -- 验证方式:`git ls-remote http://genarrative-station/git/GenarrativeAI/Genarrative.git HEAD` 应返回 HEAD;`rg "git.genarrative.world|127.0.0.1:3000/GenarrativeAI/Genarrative.git|10.2.0.10/GenarrativeAI/Genarrative.git" jenkins scripts` 不应命中流水线源码;所有相关 Jenkinsfile 仍保留单分支 refspec、浅克隆、`noTags` 和 `honorRefspec`。 +- 验证方式:Jenkins 内部 `GitSCM checkout` 日志应显示使用 HTTP 凭据或 fallback SSH 凭据,不应出现 `No credentials specified`;不带凭据访问 `http://genarrative-station/git/GenarrativeAI/Genarrative.git` 返回 `401 Unauthorized` 属于私有仓库预期;`rg "git.genarrative.world|127.0.0.1:3000/GenarrativeAI/Genarrative.git|10.2.0.10/GenarrativeAI/Genarrative.git" jenkins scripts` 不应命中流水线源码;所有相关 Jenkinsfile 仍保留单分支 refspec、浅克隆、`noTags` 和 `honorRefspec`。 - 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/project-memory/shared-memory/pitfalls.md`。 ## 2026-06-10 dev Gitea 提供内网 HTTP 入口 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 285336c23..9771b9fc0 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -1471,11 +1471,11 @@ ## Jenkins 生产流水线拉 Git 统一走 genarrative-station -- 后续更新:2026-06-19 起常规构建 / 导入导出 / Full Build 流水线的 Jenkinsfile 内部 checkout 统一使用 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`,不再配置公网 Git fallback;`Genarrative-Server-Provision` 仍是服务器初始化专用口径,Job 的 `Pipeline script from SCM` 和 Jenkinsfile 内部 checkout 都必须使用本机路径或目标 agent 可访问的内网 Git 源。 +- 后续更新:2026-06-19 起常规构建 / 导入导出 / Full Build 流水线的 Jenkinsfile 内部 checkout 统一使用 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`,并显式传入 Jenkins HTTP 凭据 `genarrative-local-gitea-http`;HTTP 凭据缺失或鉴权失败时允许回退到内网 SSH 地址 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git` 和凭据 `genarrative-local-gitea-ssh`,不再配置公网 Git fallback;`Genarrative-Server-Provision` 仍是服务器初始化专用口径,Job 的 `Pipeline script from SCM` 和 Jenkinsfile 内部 checkout 都必须使用本机路径或目标 agent 可访问的内网 Git 源。 - 现象:生产发布、数据库导入导出、服务器配置、构建或 `Genarrative-Full-Build-And-Deploy` 流水线执行 `GitSCM checkout` 时,如果 Jenkins 生成的 fetch 是 `+refs/heads/*:refs/remotes/origin/*`,公网 Git 链路可能在收包阶段以 `git-remote-https died of signal 15`、`curl 56 GnuTLS recv error (-9)`、`early EOF`、`invalid index-pack output` 失败;写死 `127.0.0.1:3000` 也会在当前执行 agent 不是 Gitea 所在机器时失败。 -- 原因:`127.0.0.1` 只代表当前执行阶段的 agent 自身;公网域名会绕外部链路并受公网代理、TLS、带宽和凭据影响。即使只使用内网 Git,如果 `GitSCM` 没有显式 refspec 并开启 `CloneOption honorRefspec=true`,Jenkins Git 插件也会拉取所有分支。 -- 处理:运行于 `linux && genarrative-build` 的 `Genarrative-Full-Build-And-Deploy` 源码解析阶段、`Genarrative-Web-Build` / `Genarrative-Api-Build` / `Genarrative-Stdb-Module-Build` checkout 阶段,以及数据库导入导出流水线的首次 `checkout([$class: 'GitSCM', ...])` 层统一使用 `GIT_REMOTE_URL=http://genarrative-station/git/GenarrativeAI/Genarrative.git`,`GIT_REMOTE_FALLBACK_URL` 留空;没有备用地址时主地址失败就直接失败,不再悄悄回退公网。这些首次 checkout 都必须使用目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`。后续统一走 `scripts/jenkins-checkout-source.sh`,该脚本也只按传入的有效远端候选重新 fetch 并把 `origin` 切到实际可用地址;`COMMIT_HASH` 为空时继续 `--depth=1 --no-tags`,指定 commit 时也先保持 `depth=1` 校验,浅历史无法证明归属时才按 `GENARRATIVE_JENKINS_CHECKOUT_DEEPEN_STEPS` 逐步加深,最后才展开完整历史。发布流水线不得为了缩短 checkout 时间清空上游构建传入的 `COMMIT_HASH`。 -- 验证:扫描本地 Jenkins live job `config.xml`,确认 SCM `` 不再指向 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;扫描所有生产 Jenkinsfile 的首次 `GitSCM checkout`,确认 `GIT_REMOTE_URL` 是 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`、没有公网 fallback,`userRemoteConfigs` 带 `+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}`,`CloneOption` 带 `honorRefspec: true`;扫描发布流水线确认传给 `scripts/jenkins-checkout-source.sh` 的 `COMMIT_HASH` 未被硬编码为空;运行 `bash -n scripts/jenkins-checkout-source.sh`。 +- 原因:`127.0.0.1` 只代表当前执行阶段的 agent 自身;公网域名会绕外部链路并受公网代理、TLS、带宽和凭据影响。`genarrative-station` HTTP 入口是私有 Gitea 仓库,不带 Jenkins 凭据时 Git 插件日志会显示 `No credentials specified` 并以 `Failed to authenticate user` 失败。即使只使用内网 Git,如果 `GitSCM` 没有显式 refspec 并开启 `CloneOption honorRefspec=true`,Jenkins Git 插件也会拉取所有分支。 +- 处理:运行于 `linux && genarrative-build` 的 `Genarrative-Full-Build-And-Deploy` 源码解析阶段、`Genarrative-Web-Build` / `Genarrative-Api-Build` / `Genarrative-Stdb-Module-Build` checkout 阶段,以及数据库导入导出流水线的首次 `checkout([$class: 'GitSCM', ...])` 层统一使用 `GIT_REMOTE_URL=http://genarrative-station/git/GenarrativeAI/Genarrative.git` 和 `GIT_REMOTE_CREDENTIAL_ID=genarrative-local-gitea-http`;`GIT_REMOTE_FALLBACK_URL` 只允许填内网 SSH 地址 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git`,并配套 `GIT_REMOTE_FALLBACK_CREDENTIAL_ID=genarrative-local-gitea-ssh`。这些首次 checkout 都必须使用目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`。后续统一走 `scripts/jenkins-checkout-source.sh`,构建类流水线以 `GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT=true` 复用首次 `GitSCM` 带凭据浅克隆,只有指定 commit 不在浅克隆里时才通过 SSH 凭据继续 fetch 和加深;`COMMIT_HASH` 为空时继续 `--depth=1 --no-tags`,指定 commit 时也先保持 `depth=1` 校验,浅历史无法证明归属时才按 `GENARRATIVE_JENKINS_CHECKOUT_DEEPEN_STEPS` 逐步加深,最后才展开完整历史。发布流水线不得为了缩短 checkout 时间清空上游构建传入的 `COMMIT_HASH`。 +- 验证:扫描本地 Jenkins live job `config.xml`,确认 SCM `` 不再指向 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;扫描所有生产 Jenkinsfile 的首次 `GitSCM checkout`,确认 `GIT_REMOTE_URL` 是 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`、`GIT_REMOTE_CREDENTIAL_ID` 是 `genarrative-local-gitea-http`、SSH fallback 使用 `genarrative-local-gitea-ssh`,`userRemoteConfigs` 带 `+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}`,`CloneOption` 带 `honorRefspec: true`;重放 Jenkins 时 checkout 日志不应再出现 `No credentials specified`;扫描发布流水线确认传给 `scripts/jenkins-checkout-source.sh` 的 `COMMIT_HASH` 未被硬编码为空;运行 `bash -n scripts/jenkins-checkout-source.sh`。 - 关联:`jenkins/Jenkinsfile.production-full-build-and-deploy`、`jenkins/Jenkinsfile.production-web-build`、`jenkins/Jenkinsfile.production-api-build`、`jenkins/Jenkinsfile.production-stdb-module-build`、`jenkins/Jenkinsfile.production-web-deploy`、`jenkins/Jenkinsfile.production-api-deploy`、`jenkins/Jenkinsfile.production-stdb-module-publish`、`jenkins/Jenkinsfile.production-server-provision`、`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`、`scripts/jenkins-checkout-source.sh`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。 ## Jenkins 可选参数在 set -u 下不能裸读 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index f78426555..c75fe7dda 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -369,11 +369,11 @@ Pingora current release 自审脚本 `scripts/ops/pingora-current-release-audit. `Genarrative-Web-Build` 打包 `web.tar.gz` 前、`Genarrative-Web-Deploy` 解包后都会把 Web 静态目录规范为目录 `755`、文件 `644`。如果前端页面能打开但 public 图片、字体或音频返回 `403 Forbidden`,优先检查当前 `/srv/genarrative/web` 指向的 release 中对应文件权限是否被异常归档为 `600`,临时恢复可对该 release 的 `web` 目录执行目录 `755`、文件 `644` 的权限修正。 -生产 Jenkins 的 `Pipeline script from SCM` 由 Jenkins controller 读取 Jenkinsfile。`Genarrative-Server-Provision` 是服务器初始化流水线,Job 配置里的 SCM URL 必须使用 controller 本机可访问的仓库路径或内网 Gitea 地址,不能使用 `https://git.genarrative.world/...`;否则日志一开始的 `Checking out git ... to read jenkins/Jenkinsfile.production-server-provision` 就会先从公网拉 Jenkinsfile。构建类流水线的 Jenkinsfile 内部 checkout 统一使用 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`,不再配置 `https://git.genarrative.world/...` 公网 fallback;运行节点必须确保 `genarrative-station` 能解析到可访问的 Gitea Nginx 入口,且 Git / shell 代理不拦截该单标签内网主机。所有 `GitSCM checkout` 都必须保留单分支 refspec、`shallow=true`、`depth=1`、`noTags=true` 与 `honorRefspec=true`。API / Web / Stdb 发布类流水线不在目标机器 checkout Git,统一执行上游构建归档里的部署脚本,避免产物 commit 与部署脚本 commit 漂移。 +生产 Jenkins 的 `Pipeline script from SCM` 由 Jenkins controller 读取 Jenkinsfile。`Genarrative-Server-Provision` 是服务器初始化流水线,Job 配置里的 SCM URL 必须使用 controller 本机可访问的仓库路径或内网 Gitea 地址,不能使用 `https://git.genarrative.world/...`;否则日志一开始的 `Checking out git ... to read jenkins/Jenkinsfile.production-server-provision` 就会先从公网拉 Jenkinsfile。构建类流水线的 Jenkinsfile 内部 checkout 统一使用 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`,不再配置 `https://git.genarrative.world/...` 公网 fallback;运行节点必须确保 `genarrative-station` 能解析到可访问的 Gitea Nginx 入口,且 Git / shell 代理不拦截该单标签内网主机。该 HTTP 私有仓库 checkout 必须显式传入 Jenkins 凭据 `genarrative-local-gitea-http`;凭据尚未配置或 HTTP 鉴权失败时,常规构建 / 导入导出 / Full Build 流水线允许回退到 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git`,并使用现有 `genarrative-local-gitea-ssh` 凭据,避免内部 checkout 出现 `No credentials specified` 后直接失败。所有 `GitSCM checkout` 都必须保留单分支 refspec、`shallow=true`、`depth=1`、`noTags=true` 与 `honorRefspec=true`。API / Web / Stdb 发布类流水线不在目标机器 checkout Git,统一执行上游构建归档里的部署脚本,避免产物 commit 与部署脚本 commit 漂移。 -当前 Jenkins / 本机内网 Git 入口固定为 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`,用于 controller、构建节点和本机 Agent 直接拉取仓库,避免绕公网 `git.genarrative.world`。该入口走 Gitea 的 Nginx `/git` 前缀反代;验证时执行 `git ls-remote http://genarrative-station/git/GenarrativeAI/Genarrative.git HEAD`,应能直接返回 HEAD。若机器仍保留旧的 `http://10.2.0.10/GenarrativeAI/Genarrative.git` 内网入口,只作为历史兼容和排障参考,新流水线不再默认使用。 +当前 Jenkins / 本机内网 Git 入口固定为 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`,用于 controller、构建节点和本机 Agent 直接拉取仓库,避免绕公网 `git.genarrative.world`。该入口走 Gitea 的 Nginx `/git` 前缀反代;验证时执行带有效凭据的 `git ls-remote http://genarrative-station/git/GenarrativeAI/Genarrative.git HEAD`,应能返回 HEAD;不带凭据返回 `401 Unauthorized` 说明私有仓库鉴权生效,不等同于 Git 地址不可达。若机器仍保留旧的 `http://10.2.0.10/GenarrativeAI/Genarrative.git` 内网入口,只作为历史兼容和排障参考,新流水线不再默认使用。 -`scripts/jenkins-checkout-source.sh` 是生产 Jenkinsfile 内部二次确认源码的统一入口。构建流水线和服务器初始化流水线传入 `COMMIT_HASH` 时,脚本必须先保持 `depth=1` 浅拉,若上游 commit 已在浅历史内则直接校验并 checkout;只有浅历史无法证明 commit 属于目标分支时,才按 `GENARRATIVE_JENKINS_CHECKOUT_DEEPEN_STEPS`(默认 `50 200 1000 5000`)逐步加深,最后才尝试展开完整历史。`Genarrative-Api-Deploy`、`Genarrative-Web-Deploy` 和 `Genarrative-Stdb-Module-Publish` 仍保留上游构建传入的 `COMMIT_HASH` 作为通知和追溯字段,但不再用它在目标机器重新 checkout 部署脚本。 +`scripts/jenkins-checkout-source.sh` 是生产 Jenkinsfile 内部二次确认源码的统一入口。构建流水线由 Jenkins `GitSCM checkout` 先用凭据完成浅克隆,再以 `GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT=true` 调用脚本复用当前 checkout;只有显式 `COMMIT_HASH` 不在这次浅克隆里时,脚本才通过传入的 SSH 远端继续 fetch 并逐步加深。构建流水线和服务器初始化流水线传入 `COMMIT_HASH` 时,脚本必须先保持 `depth=1` 浅拉,若上游 commit 已在浅历史内则直接校验并 checkout;只有浅历史无法证明 commit 属于目标分支时,才按 `GENARRATIVE_JENKINS_CHECKOUT_DEEPEN_STEPS`(默认 `50 200 1000 5000`)逐步加深,最后才尝试展开完整历史。`Genarrative-Api-Deploy`、`Genarrative-Web-Deploy` 和 `Genarrative-Stdb-Module-Publish` 仍保留上游构建传入的 `COMMIT_HASH` 作为通知和追溯字段,但不再用它在目标机器重新 checkout 部署脚本。 `Genarrative-Stdb-Module-Publish` 在 `Pipeline script from SCM` 阶段如果一开始就报 `No such DSL method 'pipeline'`,优先检查 `jenkins/Jenkinsfile.production-stdb-module-publish` 是否带 UTF-8 BOM。Jenkins Declarative Pipeline 的首个 token 必须是纯 `pipeline`;仓库中的 Jenkinsfile 应保存为 UTF-8 without BOM,只有临时写给 Windows PowerShell 5.1 `-File` 执行的 `.ps1` 才需要按对应 helper 转成带 BOM。验证时可检查文件前三字节不再是 `EF BB BF`,并运行 `validateDeclarativePipeline` 或重放该流水线。 diff --git a/jenkins/Jenkinsfile.production-api-build b/jenkins/Jenkinsfile.production-api-build index 856629680..54b3f3e54 100644 --- a/jenkins/Jenkinsfile.production-api-build +++ b/jenkins/Jenkinsfile.production-api-build @@ -11,7 +11,9 @@ pipeline { environment { GIT_REMOTE_URL = 'http://genarrative-station/git/GenarrativeAI/Genarrative.git' - GIT_REMOTE_FALLBACK_URL = '' + GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-http' + GIT_REMOTE_FALLBACK_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_FALLBACK_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' GENARRATIVE_API_CACHE_ROOT = 'caches/genarrative-jenkins/api-server' CARGO_INCREMENTAL = '0' RUSTC_WRAPPER = 'sccache' @@ -34,7 +36,12 @@ pipeline { stage('Checkout') { steps { script { - def checkoutFromRemote = { String remoteUrl -> + def checkoutFromRemote = { String remoteUrl, String credentialsId -> + def remoteConfig = [url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"] + def checkoutCredentialId = credentialsId?.trim() + if (checkoutCredentialId) { + remoteConfig.credentialsId = checkoutCredentialId + } checkout([ $class: 'GitSCM', branches: [[name: "*/${params.SOURCE_BRANCH}"]], @@ -43,11 +50,11 @@ pipeline { [$class: 'CleanBeforeCheckout'], [$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true], ], - userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]], + userRemoteConfigs: [remoteConfig], ]) } try { - checkoutFromRemote(env.GIT_REMOTE_URL) + checkoutFromRemote(env.GIT_REMOTE_URL, env.GIT_REMOTE_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL } catch (error) { def fallbackRemoteUrl = env.GIT_REMOTE_FALLBACK_URL?.trim() @@ -55,22 +62,26 @@ pipeline { throw error } echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${fallbackRemoteUrl}" - checkoutFromRemote(fallbackRemoteUrl) + checkoutFromRemote(fallbackRemoteUrl, env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = fallbackRemoteUrl } } - sh ''' - bash -lc ' - set -euo pipefail - chmod +x scripts/jenkins-checkout-source.sh - SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ - COMMIT_HASH="${COMMIT_HASH:-}" \ - GIT_REMOTE_URL="${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \ - GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" \ - SOURCE_COMMIT_FILE=".jenkins-source-commit" \ - scripts/jenkins-checkout-source.sh - ' - ''' + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + sh ''' + bash -lc ' + set -euo pipefail + chmod +x scripts/jenkins-checkout-source.sh + SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ + COMMIT_HASH="${COMMIT_HASH:-}" \ + GIT_REMOTE_URL="${GIT_REMOTE_FALLBACK_URL:-${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}}" \ + GIT_REMOTE_FALLBACK_URL="" \ + SOURCE_COMMIT_FILE=".jenkins-source-commit" \ + GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="true" \ + GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?GENARRATIVE_GIT_SSH_KEY 不能为空} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \ + scripts/jenkins-checkout-source.sh + ' + ''' + } script { env.SOURCE_COMMIT = readFile('.jenkins-source-commit').trim() env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER diff --git a/jenkins/Jenkinsfile.production-database-export b/jenkins/Jenkinsfile.production-database-export index 8f37ca37f..4bb9f285d 100644 --- a/jenkins/Jenkinsfile.production-database-export +++ b/jenkins/Jenkinsfile.production-database-export @@ -9,7 +9,9 @@ pipeline { environment { GIT_REMOTE_URL = 'http://genarrative-station/git/GenarrativeAI/Genarrative.git' - GIT_REMOTE_FALLBACK_URL = '' + GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-http' + GIT_REMOTE_FALLBACK_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_FALLBACK_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' } parameters { @@ -84,7 +86,12 @@ pipeline { } steps { script { - def checkoutFromRemote = { String remoteUrl -> + def checkoutFromRemote = { String remoteUrl, String credentialsId -> + def remoteConfig = [url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"] + def checkoutCredentialId = credentialsId?.trim() + if (checkoutCredentialId) { + remoteConfig.credentialsId = checkoutCredentialId + } checkout([ $class: 'GitSCM', branches: [[name: "*/${params.SOURCE_BRANCH}"]], @@ -93,11 +100,11 @@ pipeline { [$class: 'CleanBeforeCheckout'], [$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true], ], - userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]], + userRemoteConfigs: [remoteConfig], ]) } try { - checkoutFromRemote(env.GIT_REMOTE_URL) + checkoutFromRemote(env.GIT_REMOTE_URL, env.GIT_REMOTE_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL } catch (error) { def fallbackRemoteUrl = env.GIT_REMOTE_FALLBACK_URL?.trim() @@ -105,22 +112,26 @@ pipeline { throw error } echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${fallbackRemoteUrl}" - checkoutFromRemote(fallbackRemoteUrl) + checkoutFromRemote(fallbackRemoteUrl, env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = fallbackRemoteUrl } } - sh ''' - bash -lc ' - set -euo pipefail - chmod +x scripts/jenkins-checkout-source.sh - SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ - COMMIT_HASH="${COMMIT_HASH:-}" \ - GIT_REMOTE_URL="${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \ - GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" \ - SOURCE_COMMIT_FILE=".jenkins-source-commit" \ - scripts/jenkins-checkout-source.sh - ' - ''' + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + sh ''' + bash -lc ' + set -euo pipefail + chmod +x scripts/jenkins-checkout-source.sh + SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ + COMMIT_HASH="${COMMIT_HASH:-}" \ + GIT_REMOTE_URL="${GIT_REMOTE_FALLBACK_URL:-${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}}" \ + GIT_REMOTE_FALLBACK_URL="" \ + SOURCE_COMMIT_FILE=".jenkins-source-commit" \ + GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="true" \ + GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?GENARRATIVE_GIT_SSH_KEY 不能为空} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \ + scripts/jenkins-checkout-source.sh + ' + ''' + } script { def credentialBindings = [] if (params.TOKEN_CREDENTIAL_ID?.trim()) { diff --git a/jenkins/Jenkinsfile.production-database-import b/jenkins/Jenkinsfile.production-database-import index cc62c567a..39c2eaeed 100644 --- a/jenkins/Jenkinsfile.production-database-import +++ b/jenkins/Jenkinsfile.production-database-import @@ -9,7 +9,9 @@ pipeline { environment { GIT_REMOTE_URL = 'http://genarrative-station/git/GenarrativeAI/Genarrative.git' - GIT_REMOTE_FALLBACK_URL = '' + GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-http' + GIT_REMOTE_FALLBACK_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_FALLBACK_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' } parameters { @@ -142,7 +144,12 @@ pipeline { } steps { script { - def checkoutFromRemote = { String remoteUrl -> + def checkoutFromRemote = { String remoteUrl, String credentialsId -> + def remoteConfig = [url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"] + def checkoutCredentialId = credentialsId?.trim() + if (checkoutCredentialId) { + remoteConfig.credentialsId = checkoutCredentialId + } checkout([ $class: 'GitSCM', branches: [[name: "*/${params.SOURCE_BRANCH}"]], @@ -151,11 +158,11 @@ pipeline { [$class: 'CleanBeforeCheckout'], [$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true], ], - userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]], + userRemoteConfigs: [remoteConfig], ]) } try { - checkoutFromRemote(env.GIT_REMOTE_URL) + checkoutFromRemote(env.GIT_REMOTE_URL, env.GIT_REMOTE_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL } catch (error) { def fallbackRemoteUrl = env.GIT_REMOTE_FALLBACK_URL?.trim() @@ -163,22 +170,26 @@ pipeline { throw error } echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${fallbackRemoteUrl}" - checkoutFromRemote(fallbackRemoteUrl) + checkoutFromRemote(fallbackRemoteUrl, env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = fallbackRemoteUrl } } - sh ''' - bash -lc ' - set -euo pipefail - chmod +x scripts/jenkins-checkout-source.sh - SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ - COMMIT_HASH="${COMMIT_HASH:-}" \ - GIT_REMOTE_URL="${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \ - GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" \ - SOURCE_COMMIT_FILE=".jenkins-source-commit" \ - scripts/jenkins-checkout-source.sh - ' - ''' + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + sh ''' + bash -lc ' + set -euo pipefail + chmod +x scripts/jenkins-checkout-source.sh + SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ + COMMIT_HASH="${COMMIT_HASH:-}" \ + GIT_REMOTE_URL="${GIT_REMOTE_FALLBACK_URL:-${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}}" \ + GIT_REMOTE_FALLBACK_URL="" \ + SOURCE_COMMIT_FILE=".jenkins-source-commit" \ + GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="true" \ + GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?GENARRATIVE_GIT_SSH_KEY 不能为空} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \ + scripts/jenkins-checkout-source.sh + ' + ''' + } script { if (params.INPUT_SOURCE == 'pipeline_archive') { echo "[database-import] 使用归档数据源: job=${params.EXPORT_JOB_NAME}, build=${params.EXPORT_BUILD_NUMBER_TO_IMPORT}, file=${env.EFFECTIVE_PIPELINE_ARCHIVE_INPUT_FILE}" diff --git a/jenkins/Jenkinsfile.production-full-build-and-deploy b/jenkins/Jenkinsfile.production-full-build-and-deploy index 15006bb8f..9d8c0fb38 100644 --- a/jenkins/Jenkinsfile.production-full-build-and-deploy +++ b/jenkins/Jenkinsfile.production-full-build-and-deploy @@ -13,7 +13,9 @@ pipeline { environment { GIT_REMOTE_URL = 'http://genarrative-station/git/GenarrativeAI/Genarrative.git' - GIT_REMOTE_FALLBACK_URL = '' + GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-http' + GIT_REMOTE_FALLBACK_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_FALLBACK_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' } parameters { @@ -44,7 +46,12 @@ pipeline { } steps { script { - def checkoutFromRemote = { String remoteUrl -> + def checkoutFromRemote = { String remoteUrl, String credentialsId -> + def remoteConfig = [url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"] + def checkoutCredentialId = credentialsId?.trim() + if (checkoutCredentialId) { + remoteConfig.credentialsId = checkoutCredentialId + } checkout([ $class: 'GitSCM', branches: [[name: "*/${params.SOURCE_BRANCH}"]], @@ -53,11 +60,11 @@ pipeline { [$class: 'CleanBeforeCheckout'], [$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true], ], - userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]], + userRemoteConfigs: [remoteConfig], ]) } try { - checkoutFromRemote(env.GIT_REMOTE_URL) + checkoutFromRemote(env.GIT_REMOTE_URL, env.GIT_REMOTE_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL } catch (error) { def fallbackRemoteUrl = env.GIT_REMOTE_FALLBACK_URL?.trim() @@ -65,22 +72,26 @@ pipeline { throw error } echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${fallbackRemoteUrl}" - checkoutFromRemote(fallbackRemoteUrl) + checkoutFromRemote(fallbackRemoteUrl, env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = fallbackRemoteUrl } } - sh ''' - bash -lc ' - set -euo pipefail - chmod +x scripts/jenkins-checkout-source.sh - SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ - COMMIT_HASH="${COMMIT_HASH:-}" \ - GIT_REMOTE_URL="${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \ - GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" \ - SOURCE_COMMIT_FILE=".jenkins-source-commit" \ - scripts/jenkins-checkout-source.sh - ' - ''' + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + sh ''' + bash -lc ' + set -euo pipefail + chmod +x scripts/jenkins-checkout-source.sh + SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ + COMMIT_HASH="${COMMIT_HASH:-}" \ + GIT_REMOTE_URL="${GIT_REMOTE_FALLBACK_URL:-${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}}" \ + GIT_REMOTE_FALLBACK_URL="" \ + SOURCE_COMMIT_FILE=".jenkins-source-commit" \ + GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="true" \ + GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?GENARRATIVE_GIT_SSH_KEY 不能为空} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \ + scripts/jenkins-checkout-source.sh + ' + ''' + } script { env.SOURCE_COMMIT = readFile('.jenkins-source-commit').trim() env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER diff --git a/jenkins/Jenkinsfile.production-stdb-module-build b/jenkins/Jenkinsfile.production-stdb-module-build index 4c02ace9b..df6408b68 100644 --- a/jenkins/Jenkinsfile.production-stdb-module-build +++ b/jenkins/Jenkinsfile.production-stdb-module-build @@ -11,7 +11,9 @@ pipeline { environment { GIT_REMOTE_URL = 'http://genarrative-station/git/GenarrativeAI/Genarrative.git' - GIT_REMOTE_FALLBACK_URL = '' + GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-http' + GIT_REMOTE_FALLBACK_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_FALLBACK_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' GENARRATIVE_STDB_CACHE_ROOT = 'caches/genarrative-jenkins/stdb-module' CARGO_INCREMENTAL = '0' RUSTC_WRAPPER = 'sccache' @@ -35,7 +37,12 @@ pipeline { stage('Checkout') { steps { script { - def checkoutFromRemote = { String remoteUrl -> + def checkoutFromRemote = { String remoteUrl, String credentialsId -> + def remoteConfig = [url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"] + def checkoutCredentialId = credentialsId?.trim() + if (checkoutCredentialId) { + remoteConfig.credentialsId = checkoutCredentialId + } checkout([ $class: 'GitSCM', branches: [[name: "*/${params.SOURCE_BRANCH}"]], @@ -44,11 +51,11 @@ pipeline { [$class: 'CleanBeforeCheckout'], [$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true], ], - userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]], + userRemoteConfigs: [remoteConfig], ]) } try { - checkoutFromRemote(env.GIT_REMOTE_URL) + checkoutFromRemote(env.GIT_REMOTE_URL, env.GIT_REMOTE_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL } catch (error) { def fallbackRemoteUrl = env.GIT_REMOTE_FALLBACK_URL?.trim() @@ -56,22 +63,26 @@ pipeline { throw error } echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${fallbackRemoteUrl}" - checkoutFromRemote(fallbackRemoteUrl) + checkoutFromRemote(fallbackRemoteUrl, env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = fallbackRemoteUrl } } - sh ''' - bash -lc ' - set -euo pipefail - chmod +x scripts/jenkins-checkout-source.sh - SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ - COMMIT_HASH="${COMMIT_HASH:-}" \ - GIT_REMOTE_URL="${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \ - GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" \ - SOURCE_COMMIT_FILE=".jenkins-source-commit" \ - scripts/jenkins-checkout-source.sh - ' - ''' + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + sh ''' + bash -lc ' + set -euo pipefail + chmod +x scripts/jenkins-checkout-source.sh + SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ + COMMIT_HASH="${COMMIT_HASH:-}" \ + GIT_REMOTE_URL="${GIT_REMOTE_FALLBACK_URL:-${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}}" \ + GIT_REMOTE_FALLBACK_URL="" \ + SOURCE_COMMIT_FILE=".jenkins-source-commit" \ + GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="true" \ + GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?GENARRATIVE_GIT_SSH_KEY 不能为空} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \ + scripts/jenkins-checkout-source.sh + ' + ''' + } script { env.SOURCE_COMMIT = readFile('.jenkins-source-commit').trim() env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER diff --git a/jenkins/Jenkinsfile.production-web-build b/jenkins/Jenkinsfile.production-web-build index 1ca27aef6..81ad6c47d 100644 --- a/jenkins/Jenkinsfile.production-web-build +++ b/jenkins/Jenkinsfile.production-web-build @@ -11,7 +11,9 @@ pipeline { environment { GIT_REMOTE_URL = 'http://genarrative-station/git/GenarrativeAI/Genarrative.git' - GIT_REMOTE_FALLBACK_URL = '' + GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-http' + GIT_REMOTE_FALLBACK_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_FALLBACK_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' } parameters { @@ -30,7 +32,12 @@ pipeline { stage('Checkout') { steps { script { - def checkoutFromRemote = { String remoteUrl -> + def checkoutFromRemote = { String remoteUrl, String credentialsId -> + def remoteConfig = [url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"] + def checkoutCredentialId = credentialsId?.trim() + if (checkoutCredentialId) { + remoteConfig.credentialsId = checkoutCredentialId + } checkout([ $class: 'GitSCM', branches: [[name: "*/${params.SOURCE_BRANCH}"]], @@ -39,11 +46,11 @@ pipeline { [$class: 'CleanBeforeCheckout'], [$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true], ], - userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]], + userRemoteConfigs: [remoteConfig], ]) } try { - checkoutFromRemote(env.GIT_REMOTE_URL) + checkoutFromRemote(env.GIT_REMOTE_URL, env.GIT_REMOTE_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL } catch (error) { def fallbackRemoteUrl = env.GIT_REMOTE_FALLBACK_URL?.trim() @@ -51,22 +58,26 @@ pipeline { throw error } echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${fallbackRemoteUrl}" - checkoutFromRemote(fallbackRemoteUrl) + checkoutFromRemote(fallbackRemoteUrl, env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID) env.EFFECTIVE_GIT_REMOTE_URL = fallbackRemoteUrl } } - sh ''' - bash -lc ' - set -euo pipefail - chmod +x scripts/jenkins-checkout-source.sh - SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ - COMMIT_HASH="${COMMIT_HASH:-}" \ - GIT_REMOTE_URL="${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \ - GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" \ - SOURCE_COMMIT_FILE=".jenkins-source-commit" \ - scripts/jenkins-checkout-source.sh - ' - ''' + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + sh ''' + bash -lc ' + set -euo pipefail + chmod +x scripts/jenkins-checkout-source.sh + SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \ + COMMIT_HASH="${COMMIT_HASH:-}" \ + GIT_REMOTE_URL="${GIT_REMOTE_FALLBACK_URL:-${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}}" \ + GIT_REMOTE_FALLBACK_URL="" \ + SOURCE_COMMIT_FILE=".jenkins-source-commit" \ + GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="true" \ + GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?GENARRATIVE_GIT_SSH_KEY 不能为空} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \ + scripts/jenkins-checkout-source.sh + ' + ''' + } script { env.SOURCE_COMMIT = readFile('.jenkins-source-commit').trim() env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER diff --git a/scripts/jenkins-checkout-source.sh b/scripts/jenkins-checkout-source.sh index 131bcd8e9..1f4705721 100644 --- a/scripts/jenkins-checkout-source.sh +++ b/scripts/jenkins-checkout-source.sh @@ -7,12 +7,14 @@ COMMIT_HASH="${COMMIT_HASH:-}" GIT_REMOTE_URL="${GIT_REMOTE_URL:-}" GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" SOURCE_COMMIT_FILE="${SOURCE_COMMIT_FILE:-.jenkins-source-commit}" +GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="${GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT:-false}" # Windows PowerShell 5.1 的 UTF-8 输出可能带 BOM;下游参数校验前先剥离不可见字节。 SOURCE_BRANCH="$(printf "%s" "${SOURCE_BRANCH}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n')" COMMIT_HASH="$(printf "%s" "${COMMIT_HASH}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n')" GIT_REMOTE_URL="$(printf "%s" "${GIT_REMOTE_URL}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n')" GIT_REMOTE_FALLBACK_URL="$(printf "%s" "${GIT_REMOTE_FALLBACK_URL}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n')" +GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="$(printf "%s" "${GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n' | tr '[:upper:]' '[:lower:]')" if [[ ! "${SOURCE_BRANCH}" =~ ^[0-9A-Za-z._/-]+$ ]]; then echo "[jenkins-checkout-source] SOURCE_BRANCH 只能包含数字、字母、点、下划线、短横线和斜杠: ${SOURCE_BRANCH}" >&2 @@ -118,10 +120,48 @@ resolve_requested_commit_with_deepen() { resolve_requested_commit_if_on_branch "${requested_commit}" } +# 中文注释:Jenkins GitSCM 已经用凭据完成浅克隆时,优先复用当前 checkout,避免 shell 再次无凭据 fetch 私有仓库。 +resolve_existing_checkout_if_requested() { + local current_commit + local branch_commit + local requested_commit + + if [[ "${GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT}" != "true" ]]; then + return 1 + fi + + current_commit="$(git rev-parse --verify HEAD^{commit} 2>/dev/null)" || return 1 + + if [[ -n "${COMMIT_HASH}" ]]; then + requested_commit="$(resolve_requested_commit_if_on_branch "${COMMIT_HASH}")" || return 1 + if [[ "${current_commit}" != "${requested_commit}" ]]; then + return 1 + fi + printf "%s\n" "${requested_commit}" + return 0 + fi + + branch_commit="$(git rev-parse --verify "refs/remotes/origin/${SOURCE_BRANCH}^{commit}" 2>/dev/null)" || return 1 + if [[ "${current_commit}" != "${branch_commit}" ]]; then + return 1 + fi + + printf "%s\n" "${current_commit}" +} + add_git_remote_candidate "${GIT_REMOTE_URL}" add_git_remote_candidate "${GIT_REMOTE_FALLBACK_URL}" git reset --hard HEAD +if RESOLVED_COMMIT="$(resolve_existing_checkout_if_requested)"; then + git checkout --detach "${RESOLVED_COMMIT}" + git reset --hard HEAD + git clean -fd + printf "%s\n" "${RESOLVED_COMMIT}" >"${SOURCE_COMMIT_FILE}" + echo "[jenkins-checkout-source] 复用 Jenkins GitSCM checkout: branch=${SOURCE_BRANCH} commit=${RESOLVED_COMMIT}" + exit 0 +fi + if [[ "${#GIT_REMOTE_CANDIDATES[@]}" -eq 0 ]]; then fetch_source_branch "" else