446 lines
25 KiB
Plaintext
446 lines
25 KiB
Plaintext
def runWindowsPowerShell(String scriptName, String scriptBody) {
|
||
def scriptPath = ".jenkins-${scriptName}.ps1"
|
||
writeFile file: scriptPath, text: scriptBody, encoding: 'UTF-8'
|
||
bat label: "PowerShell ${scriptName}", script: """
|
||
@echo off
|
||
setlocal
|
||
set "GENARRATIVE_POWERSHELL=%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
|
||
if not exist "%GENARRATIVE_POWERSHELL%" (
|
||
echo [jenkins-powershell] powershell.exe not found: %GENARRATIVE_POWERSHELL%
|
||
exit /b 1
|
||
)
|
||
echo [jenkins-powershell] user:
|
||
whoami
|
||
echo [jenkins-powershell] workspace: %CD%
|
||
echo [jenkins-powershell] exe: %GENARRATIVE_POWERSHELL%
|
||
if not exist "%CD%\\${scriptPath}" (
|
||
echo [jenkins-powershell] script not found: %CD%\\${scriptPath}
|
||
exit /b 1
|
||
)
|
||
"%GENARRATIVE_POWERSHELL%" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "try { \$path = Join-Path (Get-Location).ProviderPath '${scriptPath}'; Write-Host '[jenkins-powershell] script:' \$path; \$text = [System.IO.File]::ReadAllText(\$path, [System.Text.Encoding]::UTF8); Write-Host '[jenkins-powershell] loaded bytes:' ([System.IO.File]::ReadAllBytes(\$path).Length); \$scriptBlock = [ScriptBlock]::Create(\$text); & \$scriptBlock; if (\$LASTEXITCODE -is [int] -and \$LASTEXITCODE -ne 0) { exit \$LASTEXITCODE } } catch { Write-Host '[jenkins-powershell] failed:' \$_.Exception.Message; if (\$_.ScriptStackTrace) { Write-Host \$_.ScriptStackTrace }; exit 1 }"
|
||
exit /b %ERRORLEVEL%
|
||
"""
|
||
}
|
||
|
||
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'
|
||
GIT_REMOTE_FALLBACK_URL = 'https://git.genarrative.world/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')
|
||
string(name: 'NOTIFICATION_EMAILS', defaultValue: '', description: '本次运行追加通知邮箱;会与 Jenkins Secret Text 凭据 genarrative-notification-emails 合并发送')
|
||
booleanParam(name: 'CONFIRM_PROVISION', defaultValue: false, description: '确认执行服务器初始化;未勾选时只允许 dry-run')
|
||
booleanParam(name: 'DRY_RUN', defaultValue: true, description: '只打印将执行的服务器初始化命令,不写入系统配置')
|
||
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '部署脚本来源分支')
|
||
string(name: 'COMMIT_HASH', defaultValue: '', description: '部署脚本来源 commit')
|
||
string(name: 'SERVER_NAME', defaultValue: 'genarrative.example.com', description: '证书主域名;也作为 Nginx server_name 的第一个域名')
|
||
string(name: 'SERVER_ALIASES', defaultValue: '', description: '可选,额外 Nginx server_name,多个用空格或逗号分隔,例如 www.genarrative.world')
|
||
string(name: 'PROVISION_DOWNLOADS_DIR', defaultValue: 'provision-tool-downloads', description: 'Windows 下载阶段暂存 SpacetimeDB/otelcol 安装包的工作区相对目录')
|
||
string(name: 'PROVISION_TOOLS_DIR', defaultValue: 'provision-tools', description: '目标机工作区内由已下载安装包生成的工具包目录')
|
||
string(name: 'PROVISION_DOWNLOAD_PROXY', defaultValue: '', description: '可选,Windows 下载 SpacetimeDB 和 otelcol-contrib 时使用的代理地址,例如 http://127.0.0.1:7890;留空不设置代理')
|
||
string(name: 'SPACETIME_DOWNLOAD_ROOT', defaultValue: 'https://github.com/clockworklabs/SpacetimeDB/releases/latest/download', description: 'Windows 下载 SpacetimeDB Linux release tarball 的根地址;目标机不访问该地址')
|
||
string(name: 'SPACETIME_TARGET_HOST', defaultValue: 'x86_64-unknown-linux-gnu', description: '目标机 SpacetimeDB 预编译包 host triple,development/release Linux amd64 使用默认值')
|
||
string(name: 'SPACETIME_ROOT', defaultValue: '/stdb', description: 'SpacetimeDB root-dir')
|
||
string(name: 'RELEASE_ROOT', defaultValue: '/opt/genarrative/releases', description: 'release 根目录')
|
||
string(name: 'CURRENT_LINK', defaultValue: '/opt/genarrative/current', description: '当前版本软链接')
|
||
string(name: 'WEB_LINK', defaultValue: '/srv/genarrative/web', description: 'Nginx 静态站点目录或软链接')
|
||
string(name: 'API_ENV_FILE', defaultValue: '/etc/genarrative/api-server.env', description: 'api-server 环境文件')
|
||
string(name: 'API_PORT', defaultValue: '8082', description: 'api-server 本机监听端口')
|
||
choice(name: 'NGINX_CONFIG_MODE', choices: ['none', 'production-https', 'development-http'], description: 'Nginx 配置模式;开发服无域名时选 development-http,release 正式入口选 production-https')
|
||
booleanParam(name: 'ENABLE_SERVICES', defaultValue: true, description: '启用并启动 spacetimedb 与 api-server systemd 服务')
|
||
booleanParam(name: 'ENABLE_OTELCOL', defaultValue: true, description: '安装并启用本机 OpenTelemetry Collector;api-server 模板默认开启 OTLP,如需关闭请在 API_ENV_FILE 中将 GENARRATIVE_OTEL_ENABLED 改为 false')
|
||
string(name: 'OTELCOL_VERSION', defaultValue: '0.151.0', description: 'otelcol-contrib 版本')
|
||
}
|
||
|
||
stages {
|
||
stage('Prepare') {
|
||
agent {
|
||
label 'windows'
|
||
}
|
||
steps {
|
||
script {
|
||
if (params.DEPLOY_TARGET == 'release' && !params.CONFIRM_RELEASE_DEPLOY_AGENT) {
|
||
error('release provision 需要先配置独立 release 部署 agent,并勾选 CONFIRM_RELEASE_DEPLOY_AGENT。')
|
||
}
|
||
if (!params.DRY_RUN && !params.CONFIRM_PROVISION) {
|
||
error('执行服务器初始化前必须勾选 CONFIRM_PROVISION;否则请保持 DRY_RUN=true。')
|
||
}
|
||
if (!params.SERVER_NAME?.trim()) {
|
||
error('SERVER_NAME 不能为空。')
|
||
}
|
||
if (!(params.SERVER_NAME.trim() ==~ /^[A-Za-z0-9][A-Za-z0-9.-]*$/)) {
|
||
error("SERVER_NAME 只能填写单个域名或 IP,不能包含空格、路径或协议: ${params.SERVER_NAME}")
|
||
}
|
||
def serverAliases = params.SERVER_ALIASES?.trim()
|
||
if (serverAliases) {
|
||
serverAliases.split(/[,\s]+/).findAll { it }.each { aliasName ->
|
||
if (!(aliasName ==~ /^[A-Za-z0-9][A-Za-z0-9.-]*$/)) {
|
||
error("SERVER_ALIASES 只能填写域名或 IP,多个用空格或逗号分隔: ${aliasName}")
|
||
}
|
||
}
|
||
}
|
||
if (!params.PROVISION_TOOLS_DIR?.trim()) {
|
||
error('PROVISION_TOOLS_DIR 不能为空。')
|
||
}
|
||
if (!(params.PROVISION_TOOLS_DIR.trim() ==~ /^[0-9A-Za-z._\/-]+$/) || params.PROVISION_TOOLS_DIR.startsWith('/') || params.PROVISION_TOOLS_DIR.contains('..') || params.PROVISION_TOOLS_DIR.trim() == '.') {
|
||
error("PROVISION_TOOLS_DIR 只能是工作区内的相对目录,不能包含绝对路径或连续点号: ${params.PROVISION_TOOLS_DIR}")
|
||
}
|
||
if (!params.PROVISION_DOWNLOADS_DIR?.trim()) {
|
||
error('PROVISION_DOWNLOADS_DIR 不能为空。')
|
||
}
|
||
if (!(params.PROVISION_DOWNLOADS_DIR.trim() ==~ /^[0-9A-Za-z._\/-]+$/) || params.PROVISION_DOWNLOADS_DIR.startsWith('/') || params.PROVISION_DOWNLOADS_DIR.contains('..') || params.PROVISION_DOWNLOADS_DIR.trim() == '.') {
|
||
error("PROVISION_DOWNLOADS_DIR 只能是工作区内的相对目录,不能包含绝对路径或连续点号: ${params.PROVISION_DOWNLOADS_DIR}")
|
||
}
|
||
def provisionToolsDir = params.PROVISION_TOOLS_DIR.trim()
|
||
def provisionDownloadsDir = params.PROVISION_DOWNLOADS_DIR.trim()
|
||
if (provisionToolsDir == provisionDownloadsDir || provisionDownloadsDir.startsWith("${provisionToolsDir}/")) {
|
||
error("PROVISION_DOWNLOADS_DIR 不能等于或位于 PROVISION_TOOLS_DIR 内,否则目标机生成工具包时会删除下载缓存: ${provisionDownloadsDir}")
|
||
}
|
||
def provisionDownloadProxy = params.PROVISION_DOWNLOAD_PROXY?.trim()
|
||
if (provisionDownloadProxy && !(provisionDownloadProxy ==~ /^https?:\/\/\S+$/)) {
|
||
error("PROVISION_DOWNLOAD_PROXY 只能填写 http:// 或 https:// 开头的代理地址,当前值: ${params.PROVISION_DOWNLOAD_PROXY}")
|
||
}
|
||
if (!(params.OTELCOL_VERSION?.trim() ==~ /^[0-9]+\.[0-9]+\.[0-9]+$/)) {
|
||
error("OTELCOL_VERSION 格式应为 x.y.z: ${params.OTELCOL_VERSION}")
|
||
}
|
||
if (!(params.SPACETIME_DOWNLOAD_ROOT?.trim() ==~ /^https?:\/\/\S+$/)) {
|
||
error('SPACETIME_DOWNLOAD_ROOT 不能为空。')
|
||
}
|
||
if (!(params.SPACETIME_TARGET_HOST?.trim() ==~ /^[0-9A-Za-z._-]+$/)) {
|
||
error("SPACETIME_TARGET_HOST 只能包含字母、数字、点号、下划线和短横线: ${params.SPACETIME_TARGET_HOST}")
|
||
}
|
||
def nginxMode = params.NGINX_CONFIG_MODE?.trim()
|
||
if (!(nginxMode in ['none', 'production-https', 'development-http'])) {
|
||
error("NGINX_CONFIG_MODE 只能是 none、production-https 或 development-http,当前值: ${params.NGINX_CONFIG_MODE}")
|
||
}
|
||
if (params.DEPLOY_TARGET == 'release' && nginxMode == 'development-http') {
|
||
error('release 目标禁止安装 development-http Nginx 配置;无证书初始化请使用 NGINX_CONFIG_MODE=none。')
|
||
}
|
||
if (!params.DRY_RUN && nginxMode == 'production-https' && params.SERVER_NAME?.trim() == 'genarrative.example.com') {
|
||
error('真实初始化安装 Nginx 配置时必须把 SERVER_NAME 改成真实域名,不能使用 genarrative.example.com 占位值。证书未准备好时请先保持 NGINX_CONFIG_MODE=none。')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Download Provision Tool Archives') {
|
||
agent {
|
||
label 'windows'
|
||
}
|
||
steps {
|
||
script {
|
||
runWindowsPowerShell('server-provision-tool-downloads', '''
|
||
$ErrorActionPreference = 'Stop'
|
||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||
|
||
$downloadsDir = if ($env:PROVISION_DOWNLOADS_DIR) { $env:PROVISION_DOWNLOADS_DIR } else { 'provision-tool-downloads' }
|
||
$otelVersion = if ($env:OTELCOL_VERSION) { $env:OTELCOL_VERSION } else { '0.151.0' }
|
||
$prepareOtel = if ($env:ENABLE_OTELCOL) { $env:ENABLE_OTELCOL } else { 'true' }
|
||
$otelRoot = if ($env:OTELCOL_DOWNLOAD_ROOT) { $env:OTELCOL_DOWNLOAD_ROOT.TrimEnd('/') } else { 'https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download' }
|
||
$spacetimeDownloadRoot = if ($env:SPACETIME_DOWNLOAD_ROOT) { $env:SPACETIME_DOWNLOAD_ROOT.TrimEnd('/') } else { 'https://github.com/clockworklabs/SpacetimeDB/releases/latest/download' }
|
||
$spacetimeTargetHost = if ($env:SPACETIME_TARGET_HOST) { $env:SPACETIME_TARGET_HOST } else { 'x86_64-unknown-linux-gnu' }
|
||
$downloadProxy = if ($env:PROVISION_DOWNLOAD_PROXY) { $env:PROVISION_DOWNLOAD_PROXY } else { '' }
|
||
$workspace = (Get-Location).ProviderPath
|
||
if ([System.IO.Path]::IsPathRooted($downloadsDir)) {
|
||
throw "[prepare-provision-downloads] PROVISION_DOWNLOADS_DIR 只能是工作区内相对路径: ${downloadsDir}"
|
||
}
|
||
$downloadsDir = Join-Path $workspace $downloadsDir
|
||
Write-Host "[prepare-provision-downloads] Windows workspace: ${workspace}"
|
||
Write-Host "[prepare-provision-downloads] download dir: ${downloadsDir}"
|
||
|
||
if (Test-Path -LiteralPath $downloadsDir) {
|
||
Write-Host "[prepare-provision-downloads] 复用已有下载目录: ${downloadsDir}"
|
||
} else {
|
||
New-Item -ItemType Directory -Force -Path $downloadsDir | Out-Null
|
||
Write-Host "[prepare-provision-downloads] 已创建下载目录: ${downloadsDir}"
|
||
}
|
||
|
||
if ($downloadProxy) {
|
||
$env:HTTP_PROXY = $downloadProxy
|
||
$env:HTTPS_PROXY = $downloadProxy
|
||
$env:ALL_PROXY = $downloadProxy
|
||
Write-Host "[prepare-provision-downloads] 已配置 Windows 下载代理: $($downloadProxy -replace '://.*', '://***')"
|
||
}
|
||
|
||
function Get-GithubReleaseAssetDigest {
|
||
param(
|
||
[Parameter(Mandatory=$true)][string]$Repository,
|
||
[Parameter(Mandatory=$true)][string]$ReleaseSelector,
|
||
[Parameter(Mandatory=$true)][string]$AssetName
|
||
)
|
||
|
||
$request = @{
|
||
Uri = "https://api.github.com/repos/${Repository}/${ReleaseSelector}"
|
||
Headers = @{
|
||
Accept = 'application/vnd.github+json'
|
||
'User-Agent' = 'Genarrative-Server-Provision'
|
||
}
|
||
ErrorAction = 'Stop'
|
||
}
|
||
if ($downloadProxy) {
|
||
$request.Proxy = $downloadProxy
|
||
}
|
||
|
||
Write-Host "[prepare-provision-downloads] 查询 GitHub digest: repo=${Repository} release=${ReleaseSelector} asset=${AssetName}"
|
||
$release = Invoke-RestMethod @request
|
||
$asset = $release.assets | Where-Object { $_.name -eq $AssetName } | Select-Object -First 1
|
||
if (-not $asset) {
|
||
throw "[prepare-provision-downloads] GitHub release 未找到资产: ${Repository}/${AssetName}"
|
||
}
|
||
if (-not $asset.digest) {
|
||
throw "[prepare-provision-downloads] GitHub release 未返回 digest: ${Repository}/${AssetName}"
|
||
}
|
||
Write-Host "[prepare-provision-downloads] GitHub digest ${AssetName}: $($asset.digest)"
|
||
return $asset.digest
|
||
}
|
||
|
||
function Get-FileDigest {
|
||
param(
|
||
[Parameter(Mandatory=$true)][string]$Path,
|
||
[Parameter(Mandatory=$true)][string]$Algorithm
|
||
)
|
||
|
||
$fileHash = Get-FileHash -Algorithm $Algorithm -LiteralPath $Path
|
||
return $fileHash.Hash.ToLowerInvariant()
|
||
}
|
||
|
||
function Test-DownloadDigestMatch {
|
||
param(
|
||
[Parameter(Mandatory=$true)][string]$Path,
|
||
[Parameter(Mandatory=$true)][string]$ExpectedDigest
|
||
)
|
||
|
||
$parts = $ExpectedDigest.Split(':', 2)
|
||
if ($parts.Length -ne 2) {
|
||
throw "[prepare-provision-downloads] 无法解析 GitHub digest: ${ExpectedDigest}"
|
||
}
|
||
$algorithm = $parts[0].Trim().ToLowerInvariant()
|
||
$expectedHash = $parts[1].Trim().ToLowerInvariant()
|
||
if ($algorithm -ne 'sha256') {
|
||
throw "[prepare-provision-downloads] 暂不支持的 GitHub digest 算法: ${algorithm}"
|
||
}
|
||
$localHash = Get-FileDigest -Path $Path -Algorithm 'SHA256'
|
||
return $localHash -eq $expectedHash
|
||
}
|
||
|
||
function Invoke-ProvisionDownload {
|
||
param(
|
||
[Parameter(Mandatory=$true)][string]$Label,
|
||
[Parameter(Mandatory=$true)][string]$Url,
|
||
[Parameter(Mandatory=$true)][string]$Output,
|
||
[string]$ExpectedDigest = ''
|
||
)
|
||
|
||
if ($ExpectedDigest) {
|
||
if (Test-Path -LiteralPath $Output) {
|
||
if (Test-DownloadDigestMatch -Path $Output -ExpectedDigest $ExpectedDigest) {
|
||
$existingItem = Get-Item -LiteralPath $Output
|
||
Write-Host "[prepare-provision-downloads] 已存在且校验一致,跳过下载: ${Label} bytes=$($existingItem.Length) path=${Output}"
|
||
return
|
||
}
|
||
Write-Host "[prepare-provision-downloads] 已存在但校验不一致,重新下载: ${Label} path=${Output}"
|
||
}
|
||
}
|
||
|
||
Write-Host "[prepare-provision-downloads] 下载 ${Label}: ${Url}"
|
||
$tempOutput = "${Output}.download"
|
||
if (Test-Path -LiteralPath $tempOutput) {
|
||
Remove-Item -LiteralPath $tempOutput -Force
|
||
}
|
||
$curl = Get-Command curl.exe -ErrorAction SilentlyContinue
|
||
if ($curl) {
|
||
$arguments = @('-fL', '--retry', '3', '--retry-delay', '2', '-o', $tempOutput)
|
||
if ($downloadProxy) {
|
||
$arguments += @('--proxy', $downloadProxy)
|
||
}
|
||
$arguments += $Url
|
||
& $curl.Source @arguments
|
||
$exitCode = $LASTEXITCODE
|
||
if ($exitCode -ne 0) {
|
||
throw "[prepare-provision-downloads] curl 下载失败: ${Label}, exit=${exitCode}"
|
||
}
|
||
} else {
|
||
$parameters = @{
|
||
Uri = $Url
|
||
OutFile = $tempOutput
|
||
UseBasicParsing = $true
|
||
}
|
||
if ($downloadProxy) {
|
||
$parameters.Proxy = $downloadProxy
|
||
}
|
||
Invoke-WebRequest @parameters
|
||
}
|
||
|
||
$item = Get-Item -LiteralPath $tempOutput
|
||
if ($item.Length -le 0) {
|
||
throw "[prepare-provision-downloads] 下载结果为空: ${tempOutput}"
|
||
}
|
||
if ($ExpectedDigest) {
|
||
if (-not (Test-DownloadDigestMatch -Path $tempOutput -ExpectedDigest $ExpectedDigest)) {
|
||
throw "[prepare-provision-downloads] 下载结果校验失败: ${Label}"
|
||
}
|
||
}
|
||
Move-Item -LiteralPath $tempOutput -Destination $Output -Force
|
||
$finalItem = Get-Item -LiteralPath $Output
|
||
Write-Host "[prepare-provision-downloads] 已下载 ${Label}: bytes=$($finalItem.Length) path=${Output}"
|
||
}
|
||
|
||
$spacetimeArchiveName = "spacetime-${spacetimeTargetHost}.tar.gz"
|
||
$spacetimeArchiveUrl = "${spacetimeDownloadRoot}/${spacetimeArchiveName}"
|
||
$spacetimeArchiveDigest = Get-GithubReleaseAssetDigest -Repository 'clockworklabs/SpacetimeDB' -ReleaseSelector 'releases/latest' -AssetName $spacetimeArchiveName
|
||
Invoke-ProvisionDownload -Label "SpacetimeDB release tarball ${spacetimeTargetHost}" -Url $spacetimeArchiveUrl -Output (Join-Path $downloadsDir $spacetimeArchiveName) -ExpectedDigest $spacetimeArchiveDigest
|
||
|
||
if ($prepareOtel -eq 'true') {
|
||
$otelArchiveName = "otelcol-contrib_${otelVersion}_linux_amd64.tar.gz"
|
||
$otelUrl = "${otelRoot}/v${otelVersion}/${otelArchiveName}"
|
||
$otelDigest = Get-GithubReleaseAssetDigest -Repository 'open-telemetry/opentelemetry-collector-releases' -ReleaseSelector "releases/tags/v${otelVersion}" -AssetName $otelArchiveName
|
||
Invoke-ProvisionDownload -Label "otelcol-contrib ${otelVersion} linux amd64" -Url $otelUrl -Output (Join-Path $downloadsDir $otelArchiveName) -ExpectedDigest $otelDigest
|
||
} else {
|
||
Write-Host "[prepare-provision-downloads] ENABLE_OTELCOL=${prepareOtel},跳过 otelcol-contrib 下载。"
|
||
}
|
||
|
||
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
|
||
$manifest = @(
|
||
"spacetime release tarball ${spacetimeArchiveUrl}",
|
||
"spacetime target host ${spacetimeTargetHost}",
|
||
"otelcol-contrib ${otelVersion} prepare=${prepareOtel}"
|
||
)
|
||
[System.IO.File]::WriteAllLines((Join-Path $downloadsDir 'DOWNLOADS-MANIFEST.txt'), $manifest, $utf8NoBom)
|
||
|
||
Get-ChildItem -LiteralPath $downloadsDir | Sort-Object Name | ForEach-Object {
|
||
Write-Host "[prepare-provision-downloads] artifact $($_.Length) $($_.Name)"
|
||
}
|
||
''')
|
||
}
|
||
stash name: 'server-provision-tool-downloads', includes: "${params.PROVISION_DOWNLOADS_DIR}/**", useDefaultExcludes: false
|
||
}
|
||
}
|
||
|
||
stage('Checkout Provision Files') {
|
||
agent {
|
||
label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}"
|
||
}
|
||
steps {
|
||
script {
|
||
def checkoutFromRemote = { String remoteUrl ->
|
||
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: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]],
|
||
])
|
||
}
|
||
try {
|
||
checkoutFromRemote(env.GIT_REMOTE_URL)
|
||
env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL
|
||
} catch (error) {
|
||
echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${env.GIT_REMOTE_FALLBACK_URL}"
|
||
checkoutFromRemote(env.GIT_REMOTE_FALLBACK_URL)
|
||
env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_FALLBACK_URL
|
||
}
|
||
}
|
||
sh '''
|
||
bash <<'BASH'
|
||
set -euo pipefail
|
||
chmod +x scripts/jenkins-checkout-source.sh
|
||
SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \
|
||
COMMIT_HASH="${COMMIT_HASH:-${SOURCE_COMMIT:-}}" \
|
||
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
|
||
BASH
|
||
'''
|
||
script {
|
||
env.SOURCE_COMMIT = readFile('.jenkins-source-commit').trim()
|
||
echo "Provision 源码 commit=${env.SOURCE_COMMIT}"
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Provision Server') {
|
||
agent {
|
||
label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}"
|
||
}
|
||
steps {
|
||
unstash 'server-provision-tool-downloads'
|
||
sh '''
|
||
bash <<'BASH'
|
||
set -euo pipefail
|
||
chmod +x scripts/prepare-server-provision-tools.sh
|
||
PROVISION_DOWNLOADS_DIR="${PROVISION_DOWNLOADS_DIR:-provision-tool-downloads}" \
|
||
PROVISION_TOOLS_DIR="${PROVISION_TOOLS_DIR:-provision-tools}" \
|
||
OTELCOL_VERSION="${OTELCOL_VERSION:-0.151.0}" \
|
||
PREPARE_OTELCOL="${ENABLE_OTELCOL:-true}" \
|
||
PROVISION_REQUIRE_LOCAL_DOWNLOADS="true" \
|
||
SPACETIME_DOWNLOAD_ROOT="${SPACETIME_DOWNLOAD_ROOT:-https://github.com/clockworklabs/SpacetimeDB/releases/latest/download}" \
|
||
SPACETIME_TARGET_HOST="${SPACETIME_TARGET_HOST:-x86_64-unknown-linux-gnu}" \
|
||
scripts/prepare-server-provision-tools.sh
|
||
|
||
if [[ "${ENABLE_OTELCOL:-true}" == "true" ]]; then
|
||
chmod +x "${PROVISION_TOOLS_DIR:-provision-tools}/otelcol-contrib"
|
||
fi
|
||
chmod +x "${PROVISION_TOOLS_DIR:-provision-tools}/spacetime/spacetime" \
|
||
"${PROVISION_TOOLS_DIR:-provision-tools}/spacetime/bin/current/spacetimedb-cli" \
|
||
"${PROVISION_TOOLS_DIR:-provision-tools}/spacetime/bin/current/spacetimedb-standalone"
|
||
chmod +x scripts/jenkins-server-provision.sh
|
||
PROVISION_TOOLS_DIR="${PROVISION_TOOLS_DIR:-provision-tools}" \
|
||
SPACETIME_BIN_SOURCE="${PROVISION_TOOLS_DIR:-provision-tools}/spacetime/spacetime" \
|
||
OTELCOL_BIN_SOURCE="${PROVISION_TOOLS_DIR:-provision-tools}/otelcol-contrib" \
|
||
scripts/jenkins-server-provision.sh
|
||
BASH
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
|
||
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: '服务器初始化流水线结束'),
|
||
]
|
||
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 "Server provision 完成: target=${params.DEPLOY_TARGET}, dryRun=${params.DRY_RUN}, nginxConfigMode=${params.NGINX_CONFIG_MODE}"
|
||
}
|
||
}
|
||
}
|