From c8dd6cbd3336da78e5cfc8b50c8129c9d8848816 Mon Sep 17 00:00:00 2001 From: kdletters Date: Tue, 14 Jul 2026 22:03:32 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=20Jenkins=20=E6=9C=AC?= =?UTF-8?q?=E6=9C=BA=20Git=20=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将生产流水线 Git checkout 地址统一为 127.0.0.1:2222 数据库导入导出改由 Built-In Node 准备并传递脚本 新增 Git 源与目标节点边界的生产运维门禁 同步生产运维文档和项目共享记忆 --- .../shared-memory/decision-log.md | 8 ++ docs/project-memory/shared-memory/pitfalls.md | 10 +- ...发运维】本地开发验证与生产运维-2026-05-15.md | 4 +- jenkins/Jenkinsfile.production-api-build | 2 +- .../Jenkinsfile.production-database-export | 26 ++++-- .../Jenkinsfile.production-database-import | 26 ++++-- ...nkinsfile.production-full-build-and-deploy | 2 +- .../Jenkinsfile.production-server-provision | 2 +- .../Jenkinsfile.production-stdb-module-build | 2 +- jenkins/Jenkinsfile.production-web-build | 2 +- scripts/check-production-ops-guardrails.mjs | 92 +++++++++++++++++++ 11 files changed, 146 insertions(+), 30 deletions(-) diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index ad437970e..7cfce81eb 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -4094,3 +4094,11 @@ - 后台边界:充值订单、预检、执行、应急退款号登记、用户详情和钱包冻结均只挂在管理员鉴权路由。用户详情由 `user_id` 或陶泥号经认证服务解析,返回头像、昵称、脱敏手机号、绑定状态、钱包分桶、占用、欠账和最近订单;后台语义明确的用户字段复用同一个图标按钮和弹窗,管理员主体及 `admin:*` 合成 ID 不打开用户详情。 - 部分退款预检:微信支付查单 `trade_state=REFUND` 只表示已发生退款,不代表全额退款。刷新已登记退款后,本地累计成功退款大于 0 且小于订单总额、且不存在非终态退款、活动 hold、欠账或人工冻结时,可以继续退本地剩余额度;没有本地成功退款事实能解释 `REFUND` 时继续失败关闭并要求登记或账单对账。 - 影响范围:`module-runtime`、`spacetime-module`、`spacetime-client`、`api-server` 管理员 BFF / refund worker、`shared-contracts` 与 `apps/admin-web`。 + +## 2026-07-14 Jenkins Git 源收口到本机 loopback + +- 背景:Jenkins controller 与 Gitea SSH 当前同机运行,live Job 的 `Pipeline script from SCM` 已使用 `127.0.0.1:2222`,但仓库 Jenkinsfile 内部 checkout 仍固定到局域网 IP,导致入口 SCM 与执行阶段来源不一致。 +- 决策:所有生产 Job 的 SCM URL 和 Jenkinsfile 内部源码准备统一使用 `ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git`,继续使用 `genarrative-local-gitea-ssh`,不保留局域网 IP、HTTP 内网或公网 fallback。该决策覆盖 2026-06-19 的局域网 SSH 地址口径。 +- 目标机边界:`127.0.0.1` 只允许在 Jenkins controller / Built-In Node 用于 Git。数据库导入导出与 Server-Provision 都必须在带 `linux && genarrative-build` 标签的 Built-In Node 完成 checkout 和 commit 校验,再通过 stash 把必要脚本交给 dev / release 目标 agent;目标 agent 不得自行 checkout Git 或挂载 Git SSH 凭据。 +- 影响范围:生产构建、Full、数据库导入导出和 Server-Provision Jenkinsfile,生产运维文档、共享踩坑记录与生产运维静态门禁。 +- 验证方式:`npm run check:production-ops`、`npm run check:encoding`、`bash -n scripts/jenkins-checkout-source.sh`、`git diff --check`;只读核对 live Job `config.xml` 的 SCM URL,并在 Jenkins 凭据环境对 loopback SSH 地址执行 `git ls-remote ... HEAD`。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 5e31109f9..162820a43 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -1896,13 +1896,13 @@ - 验证:deploy 工作区应直接出现 `build//web.tar.gz` 与 `web.tar.gz.sha256`;后续仍由 `scripts/deploy/production-web-deploy.sh` 执行 checksum 校验和解压 smoke。 - 关联:`jenkins/Jenkinsfile.production-web-deploy`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 -## Jenkins 生产流水线拉 Git 统一走内网 SSH +## Jenkins 生产流水线拉 Git 统一走本机 SSH -- 后续更新:2026-06-19 起常规构建 / 导入导出 / Full Build 流水线的 Jenkinsfile 内部 checkout 统一使用内网 SSH 地址 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git` 和凭据 `genarrative-local-gitea-ssh`,不再把 `http://genarrative-station/git/GenarrativeAI/Genarrative.git` 作为默认主源或 fallback,也不再配置公网 Git fallback;`Genarrative-Server-Provision` 仍是服务器初始化专用口径,Job 的 `Pipeline script from SCM` 和 Jenkinsfile 内部 checkout 都必须使用本机路径或目标 agent 可访问的内网 Git 源。 +- 后续更新:2026-07-14 起所有生产 Job 的 `Pipeline script from SCM` 和 Jenkinsfile 内部 checkout 统一使用本机 SSH 地址 `ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git` 与凭据 `genarrative-local-gitea-ssh`,不再保留局域网 IP、HTTP 内网地址或公网 fallback。 - 现象:生产发布、数据库导入导出、服务器配置、构建或 `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、带宽和凭据影响。HTTP 私有仓库入口如果没有配置 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=ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git` 和 `GIT_REMOTE_CREDENTIAL_ID=genarrative-local-gitea-ssh`,`GIT_REMOTE_FALLBACK_URL` 留空。这些首次 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` 是 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git`、`GIT_REMOTE_CREDENTIAL_ID` 是 `genarrative-local-gitea-ssh`、`GIT_REMOTE_FALLBACK_URL` 为空,`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`。 +- 原因:`127.0.0.1` 只代表当前执行阶段的 agent 自身,因此 Git checkout 必须收口到同机运行 Gitea SSH、带 `linux && genarrative-build` 标签的 Jenkins Built-In Node;公网域名和局域网 IP 会引入额外网络、代理、TLS 与地址漂移。即使使用本机 Git,如果 `GitSCM` 没有显式 refspec 并开启 `CloneOption honorRefspec=true`,Jenkins Git 插件仍会拉取所有分支。 +- 处理:Full、Web、API、Stdb、Server-Provision 与数据库导入导出的源码准备统一在 Jenkins Built-In Node 使用 `GIT_REMOTE_URL=ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git` 和 `GIT_REMOTE_CREDENTIAL_ID=genarrative-local-gitea-ssh`,`GIT_REMOTE_FALLBACK_URL` 留空。数据库导入导出把经过 commit 校验的必要脚本 stash 给目标 agent,release / dev 目标阶段只 unstash,不再 checkout Git 或挂载 Git SSH 凭据。首次 checkout 保留目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`,随后由 `scripts/jenkins-checkout-source.sh` 复用并在必要时逐步加深。 +- 验证:扫描本地 Jenkins live Job `config.xml` 和所有生产 Jenkinsfile,确认 Git URL 均为 `ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git`,凭据仍为 `genarrative-local-gitea-ssh` 且 fallback 为空;确认数据库导入导出在 Prepare 阶段 checkout / stash,目标阶段只 unstash;在 Jenkins 凭据环境运行 `git ls-remote ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git HEAD`,并运行 `npm run check:production-ops`、`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 f8ab176bc..11be34970 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -466,9 +466,9 @@ Nginx 与 Pingora 在维护 marker 存在时对内网来源绕过整站维护闸 版本化默认维护页固定为 `public/maintenance.html`,使用 `public/branding/taonier-maintenance-page.png` 作为品牌视觉,只允许保存无日期、无具体时段的通用文案;正常 Web 构建由 Vite 复制到发布包根目录的 `web/maintenance.html`,并由 `check-maintenance-page.mjs` 在打包前拒绝“今天 / 今晚”、具体日期或 `HH:MM` 等临时公告。计划内停服的临时公告必须放在 release 外文件中,通过 `/opt/genarrative/current/scripts/deploy/maintenance-on.sh --page-file <公告HTML> <维护原因>` 原子安装到 `/var/lib/genarrative/maintenance/page.html`。Nginx 与 Pingora 在该文件存在时优先返回它,缺失时回退当前 Web 制品的默认维护页;同一维护窗口内 Stdb / API 的后续 `maintenance-on.sh` 调用保留已安装公告,`maintenance-off.sh` 同时删除 marker 和运行态公告,避免下次维护复活旧内容。公告启用后同时用 `genarrative.world` 与 `www.genarrative.world` 的真实 HTTPS 响应校验 `503` 和公告正文。 -生产 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。构建类流水线和 `Genarrative-Server-Provision` 的 Jenkinsfile 内部源码准备阶段统一使用 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git`,并显式传入 Jenkins SSH 凭据 `genarrative-local-gitea-ssh`;不再配置 `https://git.genarrative.world/...` 公网 fallback,也不再默认使用 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`。所有 `GitSCM checkout` 都必须保留单分支 refspec、`shallow=true`、`depth=1`、`noTags=true` 与 `honorRefspec=true`。API / Web / Stdb 发布类流水线不在目标机器 checkout Git,统一执行上游构建归档里的部署脚本,避免产物 commit 与部署脚本 commit 漂移;Server-Provision 也不在目标 dev / release agent checkout Git,而是由 Jenkins 构建节点先准备 provision 脚本与配置并上传给目标 agent。 +生产 Jenkins 的 `Pipeline script from SCM` 由 Jenkins controller 读取 Jenkinsfile。所有生产 Job 的 SCM URL,以及 Jenkinsfile 内部在 Jenkins Built-In Node 执行的源码准备,统一使用 `ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git`,并显式传入 Jenkins SSH 凭据 `genarrative-local-gitea-ssh`;不再配置局域网 IP、`https://git.genarrative.world/...` 公网 fallback 或 `http://genarrative-station/git/GenarrativeAI/Genarrative.git`。所有 `GitSCM checkout` 都必须保留单分支 refspec、`shallow=true`、`depth=1`、`noTags=true` 与 `honorRefspec=true`。API / Web / Stdb 发布类流水线不在目标机器 checkout Git,统一执行上游构建归档里的部署脚本;Server-Provision 和数据库导入导出也由带 `linux && genarrative-build` 标签的 Jenkins Built-In Node 先 checkout 并 stash 所需脚本,再交给目标 dev / release agent,避免目标机把 `127.0.0.1` 误解为远端 Gitea 或让产物 commit 与执行脚本漂移。 -当前 Jenkins / 本机内网 Git 入口固定为 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git`,用于 controller、构建节点和本机 Agent 直接拉取仓库,避免绕公网 `git.genarrative.world`。验证时在具备对应 SSH key 和 known_hosts 的环境执行 `git ls-remote ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git HEAD`,应能返回 HEAD。若机器仍保留旧的 `http://genarrative-station/git/GenarrativeAI/Genarrative.git` 或 `http://10.2.0.10/GenarrativeAI/Genarrative.git` 内网入口,只作为历史兼容和排障参考,新流水线不再默认使用。 +当前 Jenkins / 本机 Git 入口固定为 `ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git`。验证时在具备 Jenkins SSH key 和 `[127.0.0.1]:2222` known_hosts 的环境执行 `git ls-remote ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git HEAD`,应能返回 HEAD;同时扫描 live Job `config.xml` 和仓库 Jenkinsfile,确认没有残留局域网 IP 或公网 Git 地址。旧的局域网、公网和 HTTP 内网入口只作为历史兼容与排障参考,新流水线不再默认使用。 `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 部署脚本。 diff --git a/jenkins/Jenkinsfile.production-api-build b/jenkins/Jenkinsfile.production-api-build index ea9496ae6..31f67989a 100644 --- a/jenkins/Jenkinsfile.production-api-build +++ b/jenkins/Jenkinsfile.production-api-build @@ -10,7 +10,7 @@ pipeline { } environment { - GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git' GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' GIT_REMOTE_FALLBACK_URL = '' GIT_REMOTE_FALLBACK_CREDENTIAL_ID = '' diff --git a/jenkins/Jenkinsfile.production-database-export b/jenkins/Jenkinsfile.production-database-export index 550f4c2cd..cf2574bd6 100644 --- a/jenkins/Jenkinsfile.production-database-export +++ b/jenkins/Jenkinsfile.production-database-export @@ -8,7 +8,7 @@ pipeline { } environment { - GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git' GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' GIT_REMOTE_FALLBACK_URL = '' GIT_REMOTE_FALLBACK_CREDENTIAL_ID = '' @@ -77,14 +77,6 @@ pipeline { env.EFFECTIVE_SPACETIME_ROOT_DIR = spacetimeRootDir env.EFFECTIVE_SERVER_BACKUP_DIRECTORY = serverBackupDirectory ?: '' } - } - } - - stage('Export Database') { - agent { - label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}" - } - steps { script { def checkoutFromRemote = { String remoteUrl, String credentialsId -> def remoteConfig = [url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"] @@ -132,6 +124,22 @@ pipeline { ' ''' } + script { + env.SOURCE_COMMIT = readFile('.jenkins-source-commit').trim() + } + stash name: 'database-export-source', + includes: '.jenkins-source-commit,scripts/jenkins-prepare-toolchain-env.sh,scripts/deploy/maintenance-on.sh,scripts/deploy/maintenance-off.sh,scripts/spacetime-export-migration-json.mjs,scripts/spacetime-migration-common.mjs', + allowEmpty: false + } + } + + stage('Export Database') { + agent { + label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}" + } + steps { + deleteDir() + unstash 'database-export-source' script { def credentialBindings = [] if (params.TOKEN_CREDENTIAL_ID?.trim()) { diff --git a/jenkins/Jenkinsfile.production-database-import b/jenkins/Jenkinsfile.production-database-import index c2511e2d2..cdb8f7d9a 100644 --- a/jenkins/Jenkinsfile.production-database-import +++ b/jenkins/Jenkinsfile.production-database-import @@ -8,7 +8,7 @@ pipeline { } environment { - GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git' GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' GIT_REMOTE_FALLBACK_URL = '' GIT_REMOTE_FALLBACK_CREDENTIAL_ID = '' @@ -135,14 +135,6 @@ pipeline { env.PRE_IMPORT_BACKUP_DIRECTORY = backupDirectory env.EFFECTIVE_PRE_IMPORT_BACKUP_NAME = "pre-import-${env.BUILD_NUMBER}.json" } - } - } - - stage('Import Database') { - agent { - label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}" - } - steps { script { def checkoutFromRemote = { String remoteUrl, String credentialsId -> def remoteConfig = [url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"] @@ -190,6 +182,22 @@ pipeline { ' ''' } + script { + env.SOURCE_COMMIT = readFile('.jenkins-source-commit').trim() + } + stash name: 'database-import-source', + includes: '.jenkins-source-commit,scripts/jenkins-prepare-toolchain-env.sh,scripts/deploy/maintenance-on.sh,scripts/deploy/maintenance-off.sh,scripts/spacetime-export-migration-json.mjs,scripts/spacetime-import-migration-json.mjs,scripts/spacetime-migration-common.mjs', + allowEmpty: false + } + } + + stage('Import Database') { + agent { + label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}" + } + steps { + deleteDir() + unstash 'database-import-source' 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 a3883d7b9..28724d731 100644 --- a/jenkins/Jenkinsfile.production-full-build-and-deploy +++ b/jenkins/Jenkinsfile.production-full-build-and-deploy @@ -12,7 +12,7 @@ pipeline { } environment { - GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git' GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' GIT_REMOTE_FALLBACK_URL = '' GIT_REMOTE_FALLBACK_CREDENTIAL_ID = '' diff --git a/jenkins/Jenkinsfile.production-server-provision b/jenkins/Jenkinsfile.production-server-provision index e4d3976b9..e59a82fa9 100644 --- a/jenkins/Jenkinsfile.production-server-provision +++ b/jenkins/Jenkinsfile.production-server-provision @@ -8,7 +8,7 @@ pipeline { } environment { - GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git' GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' } diff --git a/jenkins/Jenkinsfile.production-stdb-module-build b/jenkins/Jenkinsfile.production-stdb-module-build index 7eb6971c7..0f376c974 100644 --- a/jenkins/Jenkinsfile.production-stdb-module-build +++ b/jenkins/Jenkinsfile.production-stdb-module-build @@ -10,7 +10,7 @@ pipeline { } environment { - GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git' GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' GIT_REMOTE_FALLBACK_URL = '' GIT_REMOTE_FALLBACK_CREDENTIAL_ID = '' diff --git a/jenkins/Jenkinsfile.production-web-build b/jenkins/Jenkinsfile.production-web-build index 63dced22f..b53b13b9f 100644 --- a/jenkins/Jenkinsfile.production-web-build +++ b/jenkins/Jenkinsfile.production-web-build @@ -10,7 +10,7 @@ pipeline { } environment { - GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git' + GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git' GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh' GIT_REMOTE_FALLBACK_URL = '' GIT_REMOTE_FALLBACK_CREDENTIAL_ID = '' diff --git a/scripts/check-production-ops-guardrails.mjs b/scripts/check-production-ops-guardrails.mjs index d5160c5a3..c33b0b5ec 100644 --- a/scripts/check-production-ops-guardrails.mjs +++ b/scripts/check-production-ops-guardrails.mjs @@ -6827,6 +6827,32 @@ const nodeEnvFileCommandFiles = [ 'scripts/deploy/pingora-direct-rollback.sh', ]; +const jenkinsSourceCheckoutFiles = [ + '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/Jenkinsfile.production-server-provision', +]; + +const jenkinsLoopbackGitRemote = + "GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git'"; + +const databaseTargetSourceStashes = [ + { + file: 'jenkins/Jenkinsfile.production-database-export', + targetStage: "stage('Export Database')", + stashName: 'database-export-source', + }, + { + file: 'jenkins/Jenkinsfile.production-database-import', + targetStage: "stage('Import Database')", + stashName: 'database-import-source', + }, +]; + let failed = false; for (const check of checks) { @@ -6845,6 +6871,72 @@ for (const check of checks) { } } +for (const file of jenkinsSourceCheckoutFiles) { + const content = readFileSync(file, 'utf8'); + if (!content.includes(jenkinsLoopbackGitRemote)) { + failed = true; + console.error( + `[check:production-ops] ${file} 的 Git checkout 必须固定使用本机 127.0.0.1:2222。`, + ); + } + if ( + !content.includes( + "GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh'", + ) + ) { + failed = true; + console.error( + `[check:production-ops] ${file} 必须继续显式使用 Jenkins Gitea SSH 凭据。`, + ); + } + if ( + content.includes('192.168.35.82') || + content.includes('git.genarrative.world') || + content.includes('genarrative-station/git') + ) { + failed = true; + console.error( + `[check:production-ops] ${file} 不得保留局域网 IP、HTTP 内网别名或公网 Git 地址。`, + ); + } +} + +for (const { file, targetStage, stashName } of databaseTargetSourceStashes) { + const content = readFileSync(file, 'utf8'); + const targetStageOffset = content.indexOf(targetStage); + const prepareStageContent = + targetStageOffset >= 0 ? content.slice(0, targetStageOffset) : ''; + const targetStageContent = + targetStageOffset >= 0 ? content.slice(targetStageOffset) : ''; + if ( + !prepareStageContent.includes("label 'linux && genarrative-build'") || + !prepareStageContent.includes("$class: 'GitSCM'") + ) { + failed = true; + console.error( + `[check:production-ops] ${file} 必须只在 Jenkins Built-In Node 的 Prepare 阶段 checkout Git。`, + ); + } + if ( + !content.includes(`stash name: '${stashName}'`) || + !targetStageContent.includes(`unstash '${stashName}'`) + ) { + failed = true; + console.error( + `[check:production-ops] ${file} 必须由本机 build 节点 stash 源码脚本,再交给数据库目标阶段。`, + ); + } + if ( + targetStageContent.includes("$class: 'GitSCM'") || + targetStageContent.includes('sshUserPrivateKey(') + ) { + failed = true; + console.error( + `[check:production-ops] ${file} 的数据库目标阶段不得再次 checkout Git 或挂载 Git SSH 凭据。`, + ); + } +} + const fullPipelineContent = readFileSync( 'jenkins/Jenkinsfile.production-full-build-and-deploy', 'utf8',