统一 Jenkins 本机 Git 源

将生产流水线 Git checkout 地址统一为 127.0.0.1:2222
数据库导入导出改由 Built-In Node 准备并传递脚本
新增 Git 源与目标节点边界的生产运维门禁
同步生产运维文档和项目共享记忆
This commit is contained in:
2026-07-14 22:03:32 +08:00
parent 34c6ed8756
commit c8dd6cbd33
11 changed files with 146 additions and 30 deletions
@@ -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`
@@ -1896,13 +1896,13 @@
- 验证:deploy 工作区应直接出现 `build/<version>/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`,不再`https://git.genarrative.world/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 `<url>` 不再指向 `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 给目标 agentrelease / 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 下不能裸读
@@ -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,也不再默认使用 `https://git.genarrative.world/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 `https://git.genarrative.world/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。若机器仍保留旧的 `https://git.genarrative.world/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 部署脚本。
+1 -1
View File
@@ -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 = ''
+17 -9
View File
@@ -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()) {
+17 -9
View File
@@ -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}"
@@ -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 = ''
@@ -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'
}
@@ -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 = ''
+1 -1
View File
@@ -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 = ''
@@ -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',