Files
Genarrative/jenkins/Jenkinsfile.production-stdb-module-build
T
kdletters 46baebbc10
Project CI / Repository checks (push) Successful in 3m8s
Project CI / Frontend tests (push) Successful in 3m26s
Project CI / Backend tests (push) Successful in 3m58s
Project CI / Native shell tests (push) Successful in 15m16s
清理过期测试与门禁
删除退役内容校验、视觉小说墓碑脚本和旧玩法门禁文档

移除死别名与退役分支触发并同步当前文档入口

为Gitea和Jenkins补齐可信SpacetimeDB schema比较基线

补充workflow、schema和生产运维防回归测试
2026-08-21 12:20:43 +08:00

234 lines
12 KiB
Plaintext

pipeline {
agent {
label 'linux && genarrative-build'
}
options {
disableConcurrentBuilds()
skipDefaultCheckout(true)
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
copyArtifactPermission('Genarrative-Stdb-Module-Publish')
}
environment {
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 = ''
GENARRATIVE_STDB_CACHE_ROOT = 'caches/genarrative-jenkins/stdb-module'
CARGO_INCREMENTAL = '0'
RUSTC_WRAPPER = 'sccache'
SCCACHE_CACHE_SIZE = '30G'
}
parameters {
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '源码分支,默认 master 最新提交')
string(name: 'COMMIT_HASH', defaultValue: '', description: '可选,指定属于 SOURCE_BRANCH 的 Git commit')
string(name: 'BUILD_VERSION', defaultValue: '', description: '发布版本号,留空则使用 Jenkins BUILD_NUMBER')
string(name: 'NOTIFICATION_EMAILS', defaultValue: '', description: '本次运行追加通知邮箱;会与 Jenkins Secret Text 凭据 genarrative-notification-emails 合并发送')
string(name: 'MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID', defaultValue: 'genarrative-spacetime-bootstrap-secret-dev-file', description: '必填:与生产模块绑定的 Jenkins Secret File 凭据 ID;仅在受控构建和发布阶段短暂挂载')
booleanParam(name: 'PUBLISH_AFTER_BUILD', defaultValue: false, description: '构建成功后是否触发 Stdb module 发布')
string(name: 'DEPLOY_JOB_NAME', defaultValue: 'Genarrative-Stdb-Module-Publish', description: 'Stdb module 发布流水线作业名')
choice(name: 'DEPLOY_TARGET', choices: ['development', 'release'], description: 'PUBLISH_AFTER_BUILD=true 时的逻辑部署目标;development 使用当前 Linux 开发/构建/开发部署 agent')
booleanParam(name: 'CONFIRM_RELEASE_DEPLOY_AGENT', defaultValue: false, description: 'PUBLISH_AFTER_BUILD=true 且目标为 release 时必须确认已有独立 release 部署 agent')
string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: '生产 SpacetimeDB database')
}
stages {
stage('Checkout') {
steps {
script {
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}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [
[$class: 'CleanBeforeCheckout'],
[$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true],
],
userRemoteConfigs: [remoteConfig],
])
}
try {
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()
if (!fallbackRemoteUrl) {
throw error
}
echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${fallbackRemoteUrl}"
checkoutFromRemote(fallbackRemoteUrl, env.GIT_REMOTE_FALLBACK_CREDENTIAL_ID)
env.EFFECTIVE_GIT_REMOTE_URL = fallbackRemoteUrl
}
}
withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_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="${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
IFS= read -r source_commit < .jenkins-source-commit
schema_base_ref="$(git rev-parse --verify "${source_commit}^" 2>/dev/null || true)"
if [[ -z "${schema_base_ref}" ]]; then
GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?GENARRATIVE_GIT_SSH_KEY 不能为空} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \
git fetch --no-tags --depth=2 \
"${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \
"${source_commit}"
schema_base_ref="$(git rev-parse --verify "${source_commit}^" 2>/dev/null || true)"
fi
if [[ -z "${schema_base_ref}" ]]; then
echo "[checkout] 无法取得待构建提交 ${source_commit} 的父提交,拒绝运行 SpacetimeDB schema 无基线门禁。" >&2
exit 1
fi
printf "%s\n" "${schema_base_ref}" > .jenkins-spacetime-schema-base
'
'''
}
script {
env.SOURCE_COMMIT = readFile('.jenkins-source-commit').trim()
env.SPACETIME_SCHEMA_BASE_REF = readFile('.jenkins-spacetime-schema-base').trim()
env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER
}
}
}
stage('Build Stdb Module') {
steps {
script {
def buildStep = {
sh '''
bash -lc '
set -euo pipefail
stdb_cache_root="${GENARRATIVE_STDB_CACHE_ROOT:-caches/genarrative-jenkins/stdb-module}"
if [[ "${stdb_cache_root}" != /* ]]; then
stdb_cache_root="${HOME:?HOME 不能为空}/${stdb_cache_root}"
fi
export CARGO_HOME="${stdb_cache_root}/cargo-home"
export CARGO_TARGET_DIR="${stdb_cache_root}/cargo-target/prod-release"
export CARGO_INCREMENTAL=0
export RUSTC_WRAPPER=sccache
export SCCACHE_DIR="${stdb_cache_root}/sccache"
export SCCACHE_CACHE_SIZE=30G
mkdir -p "${CARGO_HOME}" "${CARGO_TARGET_DIR}" "${SCCACHE_DIR}"
if [[ ! -f "${MIGRATION_BOOTSTRAP_SECRET_FILE:-}" || -L "${MIGRATION_BOOTSTRAP_SECRET_FILE}" || ! -r "${MIGRATION_BOOTSTRAP_SECRET_FILE}" ]]; then
echo "[stdb-build] 缺少受保护的 migration bootstrap secret File credential。" >&2
exit 1
fi
migration_bootstrap_secret="$(cat "${MIGRATION_BOOTSTRAP_SECRET_FILE}")"
carriage_return="$(printf "\\r")"
migration_bootstrap_secret="${migration_bootstrap_secret%${carriage_return}}"
if [[ ! "${migration_bootstrap_secret}" =~ ^[0-9a-fA-F]{64}$ ]]; then
echo "[stdb-build] migration bootstrap secret 必须是 64 位十六进制高熵值。" >&2
exit 1
fi
digest_line="$(printf "%s" "${migration_bootstrap_secret}" | sha256sum)"
export GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET_SHA256="${digest_line%% *}"
unset migration_bootstrap_secret digest_line carriage_return
trap "unset GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET_SHA256" EXIT
chmod +x scripts/jenkins-prepare-cargo-env.sh
source scripts/jenkins-prepare-cargo-env.sh
if ! command -v sccache >/dev/null 2>&1; then
echo "[stdb-build] 未找到可用 sccache,改用 rustc 直接构建。"
unset RUSTC_WRAPPER
fi
npm run check:encoding
npm run check:rustfmt
npm run check:production-ops
npm run check:server-rs-ddd
npm run check:admin-account-procedures
SOURCE_BRANCH="${SOURCE_BRANCH}" SOURCE_COMMIT="${SOURCE_COMMIT}" \
npm run build:production-release -- --component spacetime-module --name "${EFFECTIVE_BUILD_VERSION}"
'
'''
}
if (!params.MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID?.trim()) {
error('MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID 必须引用受保护的 Jenkins Secret File 凭据。')
}
withCredentials([
file(credentialsId: params.MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID.trim(), variable: 'MIGRATION_BOOTSTRAP_SECRET_FILE')
]) {
buildStep()
}
}
}
}
stage('Archive') {
steps {
archiveArtifacts artifacts: "build/${env.EFFECTIVE_BUILD_VERSION}/spacetime_module.wasm,build/${env.EFFECTIVE_BUILD_VERSION}/spacetime_module.wasm.sha256,build/${env.EFFECTIVE_BUILD_VERSION}/release-manifest.json,scripts/deploy/production-stdb-publish.sh,scripts/deploy/production-runtime-writer-identity-rotate.mjs,scripts/deploy/maintenance-on.sh,scripts/deploy/maintenance-off.sh,scripts/spacetime-migration-common.mjs,scripts/spacetime-maintain-external-generation-jobs.mjs,scripts/spacetime-clean-editor-image-asset-kind.mjs,scripts/spacetime-normalize-editor-character-actions.mjs,scripts/spacetime-migrate-editor-canvas-layout.mjs,scripts/spacetime-repair-editor-canvas-resources.mjs,scripts/database-backup-to-oss.mjs", fingerprint: true
}
}
stage('Publish') {
when {
expression { return params.PUBLISH_AFTER_BUILD }
}
steps {
build job: params.DEPLOY_JOB_NAME,
wait: true,
propagate: true,
parameters: [
string(name: 'SOURCE_BRANCH', value: params.SOURCE_BRANCH),
string(name: 'COMMIT_HASH', value: env.SOURCE_COMMIT),
string(name: 'BUILD_VERSION', value: env.EFFECTIVE_BUILD_VERSION),
string(name: 'NOTIFICATION_EMAILS', value: params.NOTIFICATION_EMAILS ?: ''),
string(name: 'MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID', value: params.MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID),
string(name: 'DEPLOY_TARGET', value: params.DEPLOY_TARGET),
booleanParam(name: 'CONFIRM_RELEASE_DEPLOY_AGENT', value: params.CONFIRM_RELEASE_DEPLOY_AGENT),
string(name: 'BUILD_JOB_NAME', value: env.JOB_NAME),
string(name: 'BUILD_NUMBER_TO_DEPLOY', value: env.BUILD_NUMBER),
string(name: 'DATABASE', value: params.DATABASE),
]
}
}
}
post {
always {
script {
def notificationParameters = [
string(name: 'SOURCE_JOB_NAME', value: env.JOB_NAME),
string(name: 'SOURCE_BUILD_NUMBER', value: env.BUILD_NUMBER),
string(name: 'SOURCE_BUILD_URL', value: env.BUILD_URL ?: ''),
string(name: 'SOURCE_RESULT', value: currentBuild.currentResult ?: 'UNKNOWN'),
string(name: 'SOURCE_BRANCH', value: params.SOURCE_BRANCH ?: ''),
string(name: 'SOURCE_COMMIT', value: env.SOURCE_COMMIT ?: (params.COMMIT_HASH ?: '')),
string(name: 'BUILD_VERSION', value: env.EFFECTIVE_BUILD_VERSION ?: (params.BUILD_VERSION ?: '')),
string(name: 'DEPLOY_TARGET', value: params.DEPLOY_TARGET ?: ''),
string(name: 'DATABASE', value: params.DATABASE ?: ''),
string(name: 'SUMMARY', value: 'Stdb module 构建流水线结束'),
]
def notificationRecipients = params.NOTIFICATION_EMAILS?.trim()
if (notificationRecipients) {
notificationParameters.add(string(name: 'EMAIL_RECIPIENTS', value: notificationRecipients))
}
try {
build job: 'Genarrative-Notify-Email',
wait: false,
propagate: false,
parameters: notificationParameters
} catch (error) {
echo "邮件通知触发失败: ${error.message}"
}
}
}
success {
echo "Stdb module 构建完成: version=${env.EFFECTIVE_BUILD_VERSION}, commit=${env.SOURCE_COMMIT}"
}
}
}