pipeline { agent none options { disableConcurrentBuilds() skipDefaultCheckout(true) buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20')) } environment { GIT_REMOTE_URL = 'http://127.0.0.1:3000/GenarrativeAI/Genarrative.git' } parameters { choice(name: 'DEPLOY_TARGET', choices: ['development', 'release'], description: '逻辑部署目标;development 使用当前 Linux 开发/构建/开发部署 agent') booleanParam(name: 'CONFIRM_RELEASE_DEPLOY_AGENT', defaultValue: false, description: '确认 release 目标已有独立 release 部署 agent;当前 Linux 开发/构建/开发部署 agent 不可冒充 release 部署机') string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '部署脚本来源分支') string(name: 'COMMIT_HASH', defaultValue: '', description: '部署脚本来源 commit;上游触发时传实际构建 commit') string(name: 'NOTIFICATION_EMAILS', defaultValue: '', description: '本次运行追加通知邮箱;会与 Jenkins Secret Text 凭据 genarrative-notification-emails 合并发送') string(name: 'BUILD_VERSION', defaultValue: '', description: '待发布版本号') string(name: 'BUILD_JOB_NAME', defaultValue: 'Genarrative-Stdb-Module-Build', description: 'Stdb module 构建流水线作业名') string(name: 'BUILD_NUMBER_TO_DEPLOY', defaultValue: '', description: '要复制归档产物的上游构建号') string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: '生产 SpacetimeDB database') string(name: 'SPACETIME_SERVER', defaultValue: 'local', description: 'SpacetimeDB server alias') string(name: 'SPACETIME_SERVER_URL', defaultValue: 'http://127.0.0.1:3101', description: '显式 SpacetimeDB server URL,填写后优先于 SPACETIME_SERVER') string(name: 'SPACETIME_ROOT_DIR', defaultValue: '/stdb', description: 'spacetime CLI root-dir;需与自托管 spacetimedb.service 一致') string(name: 'SPACETIME_RUN_AS_USER', defaultValue: 'spacetimedb', description: '执行 spacetime publish 的本机用户,默认使用自托管服务用户') booleanParam(name: 'CLEAR_DATABASE', defaultValue: false, description: '是否清空数据库后发布') } stages { stage('Prepare') { agent { label 'linux && genarrative-build' } steps { script { if (params.DEPLOY_TARGET == 'release' && !params.CONFIRM_RELEASE_DEPLOY_AGENT) { error('release 部署需要先配置独立 release 部署 agent,并勾选 CONFIRM_RELEASE_DEPLOY_AGENT。当前 Linux 开发/构建/开发部署 agent 不能执行 release 部署。') } if (!params.BUILD_VERSION?.trim()) { error('BUILD_VERSION 不能为空。') } if (!params.BUILD_JOB_NAME?.trim()) { error('BUILD_JOB_NAME 不能为空。') } if (!params.BUILD_NUMBER_TO_DEPLOY?.trim()) { error('BUILD_NUMBER_TO_DEPLOY 不能为空。') } if (!params.DATABASE?.trim()) { error('DATABASE 不能为空。') } if (!params.SPACETIME_SERVER?.trim() && !params.SPACETIME_SERVER_URL?.trim()) { error('SPACETIME_SERVER 与 SPACETIME_SERVER_URL 不能同时为空。') } def spacetimeRootDir = params.SPACETIME_ROOT_DIR?.trim() ? params.SPACETIME_ROOT_DIR.trim() : '/stdb' if (!(spacetimeRootDir ==~ /^\/(?!.*\.\.)[A-Za-z0-9._\/-]+$/)) { error("SPACETIME_ROOT_DIR 必须是 Linux 绝对路径且不能包含 ..: ${spacetimeRootDir}") } def spacetimeRunAsUser = params.SPACETIME_RUN_AS_USER?.trim() if (spacetimeRunAsUser && !(spacetimeRunAsUser ==~ /^[A-Za-z_][A-Za-z0-9_-]*$/)) { error("SPACETIME_RUN_AS_USER 只能是本机用户名: ${spacetimeRunAsUser}") } def spacetimeServerUrl = params.SPACETIME_SERVER_URL?.trim() if (spacetimeServerUrl && !(spacetimeServerUrl ==~ /^https?:\/\/[A-Za-z0-9._:-]+$/)) { error("SPACETIME_SERVER_URL 只能是 http(s) URL,且不能包含路径或 shell 特殊字符: ${spacetimeServerUrl}") } def spacetimeServer = params.SPACETIME_SERVER?.trim() if (!spacetimeServerUrl && spacetimeServer && !(spacetimeServer ==~ /^[A-Za-z0-9._:-]+$/)) { error("SPACETIME_SERVER 只能包含字母、数字、点、下划线、冒号和短横线: ${spacetimeServer}") } } } } stage('Checkout Publish Scripts') { agent { label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}" } steps { checkout([ $class: 'GitSCM', branches: [[name: "*/${params.SOURCE_BRANCH}"]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], userRemoteConfigs: [[url: "${GIT_REMOTE_URL}"]], ]) 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_URL}" \ SOURCE_COMMIT_FILE=".jenkins-source-commit" \ scripts/jenkins-checkout-source.sh ' ''' } } stage('Fetch Artifact') { agent { label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}" } steps { copyArtifacts( projectName: params.BUILD_JOB_NAME, selector: specific(params.BUILD_NUMBER_TO_DEPLOY), filter: "build/${params.BUILD_VERSION}/spacetime_module.wasm,build/${params.BUILD_VERSION}/spacetime_module.wasm.sha256,build/${params.BUILD_VERSION}/release-manifest.json", target: '.', fingerprintArtifacts: true ) } } stage('Publish Stdb Module') { agent { label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}" } steps { script { def clearArg = params.CLEAR_DATABASE ? '--clear-database' : '' def rootArg = "--root-dir \"${params.SPACETIME_ROOT_DIR?.trim() ? params.SPACETIME_ROOT_DIR.trim() : '/stdb'}\"" def runAsArg = params.SPACETIME_RUN_AS_USER?.trim() ? "--run-as-user \"${params.SPACETIME_RUN_AS_USER.trim()}\"" : '' def serverArg = params.SPACETIME_SERVER_URL?.trim() ? "--server-url \"${params.SPACETIME_SERVER_URL.trim()}\"" : "--server \"${params.SPACETIME_SERVER}\"" sh """ bash -lc ' set -euo pipefail chmod +x scripts/deploy/production-stdb-publish.sh scripts/deploy/maintenance-on.sh scripts/deploy/maintenance-off.sh scripts/deploy/production-stdb-publish.sh \\ --source-dir "build/${params.BUILD_VERSION}" \\ --database "${params.DATABASE}" \\ ${rootArg} \\ ${runAsArg} \\ ${serverArg} \\ ${clearArg} ' """ } } } } 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=${params.BUILD_VERSION}, database=${params.DATABASE}" } } }