Files
Genarrative/jenkins/Jenkinsfile.production-server-provision
2026-05-02 19:29:58 +08:00

242 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
pipeline {
agent none
options {
disableConcurrentBuilds()
timestamps()
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')
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: 'SPACETIME_BIN_SOURCE', defaultValue: '/usr/local/bin/spacetime', description: '服务器上已有 spacetime CLI 路径')
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 本机监听端口')
booleanParam(name: 'INSTALL_NGINX_CONFIG', defaultValue: true, description: '安装 Nginx 配置并执行 nginx -t')
booleanParam(name: 'ENABLE_SERVICES', defaultValue: true, description: '启用并启动 spacetimedb 与 api-server systemd 服务')
}
stages {
stage('Prepare') {
agent {
label 'linux && genarrative-build'
}
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.SPACETIME_BIN_SOURCE?.trim()) {
error('SPACETIME_BIN_SOURCE 不能为空。')
}
}
}
}
stage('Checkout Provision Files') {
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}" \
COMMIT_HASH="${COMMIT_HASH}" \
GIT_REMOTE_URL="${GIT_REMOTE_URL}" \
SOURCE_COMMIT_FILE=".jenkins-source-commit" \
scripts/jenkins-checkout-source.sh
'
'''
}
}
stage('Provision Server') {
agent {
label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-build' : 'linux && genarrative-release-deploy'}"
}
steps {
sh '''
bash -lc '
set -euo pipefail
require_path() {
local path="$1"
if [[ ! -e "${path}" ]]; then
echo "[server-provision] 缺少必要文件: ${path}" >&2
exit 1
fi
}
run_cmd() {
echo "+ $*"
if [[ "${DRY_RUN}" != "true" ]]; then
"$@"
fi
}
install_file() {
local source="$1"
local target="$2"
local mode="$3"
echo "+ install -m ${mode} ${source} ${target}"
if [[ "${DRY_RUN}" != "true" ]]; then
install -m "${mode}" "${source}" "${target}"
fi
}
render_nginx_config() {
sed "s/genarrative.example.com/${SERVER_NAME}/g" deploy/nginx/genarrative.conf
}
render_api_env_example() {
sed \
-e "s|^GENARRATIVE_API_PORT=.*|GENARRATIVE_API_PORT=${API_PORT}|" \
-e "s|^GENARRATIVE_SPACETIME_SERVER_URL=.*|GENARRATIVE_SPACETIME_SERVER_URL=http://127.0.0.1:3000|" \
deploy/env/api-server.env.example
}
escape_sed_replacement() {
printf "%s" "$1" | sed "s/[&|]/\\\\&/g"
}
render_spacetimedb_service() {
local root_escaped
root_escaped="$(escape_sed_replacement "${SPACETIME_ROOT}")"
sed \
-e "s|/stdb|${root_escaped}|g" \
deploy/systemd/spacetimedb.service
}
render_api_service() {
local current_escaped env_escaped
current_escaped="$(escape_sed_replacement "${CURRENT_LINK}")"
env_escaped="$(escape_sed_replacement "${API_ENV_FILE}")"
sed \
-e "s|/opt/genarrative/current|${current_escaped}|g" \
-e "s|/etc/genarrative/api-server.env|${env_escaped}|g" \
deploy/systemd/genarrative-api.service
}
require_path deploy/systemd/spacetimedb.service
require_path deploy/systemd/genarrative-api.service
require_path deploy/nginx/genarrative.conf
require_path deploy/nginx/snippets/genarrative-maintenance.conf
require_path deploy/env/api-server.env.example
require_path scripts/deploy/maintenance-on.sh
require_path scripts/deploy/maintenance-off.sh
require_path scripts/deploy/maintenance-status.sh
echo "[server-provision] target=${DEPLOY_TARGET}, dry_run=${DRY_RUN}, source_commit=$(cat .jenkins-source-commit)"
run_cmd id
run_cmd mkdir -p "${SPACETIME_ROOT}" "${RELEASE_ROOT}" "$(dirname "${CURRENT_LINK}")" "$(dirname "${WEB_LINK}")" /etc/genarrative /var/lib/genarrative/maintenance /var/lib/genarrative/auth
if ! id spacetimedb >/dev/null 2>&1; then
run_cmd useradd --system --home-dir "${SPACETIME_ROOT}" --shell /usr/sbin/nologin spacetimedb
else
echo "[server-provision] 用户已存在: spacetimedb"
fi
if ! id genarrative >/dev/null 2>&1; then
run_cmd useradd --system --home-dir /opt/genarrative --shell /usr/sbin/nologin genarrative
else
echo "[server-provision] 用户已存在: genarrative"
fi
run_cmd chown -R spacetimedb:spacetimedb "${SPACETIME_ROOT}"
run_cmd chown -R genarrative:genarrative /opt/genarrative /var/lib/genarrative /srv/genarrative
if [[ ! -x "${SPACETIME_BIN_SOURCE}" ]]; then
echo "[server-provision] spacetime CLI 不存在或不可执行: ${SPACETIME_BIN_SOURCE}" >&2
exit 1
fi
echo "+ install -m 0755 ${SPACETIME_BIN_SOURCE} ${SPACETIME_ROOT}/spacetime"
if [[ "${DRY_RUN}" != "true" ]]; then
install -m 0755 "${SPACETIME_BIN_SOURCE}" "${SPACETIME_ROOT}/spacetime"
chown spacetimedb:spacetimedb "${SPACETIME_ROOT}/spacetime"
fi
spacetimedb_service="$(mktemp)"
api_service="$(mktemp)"
render_spacetimedb_service >"${spacetimedb_service}"
render_api_service >"${api_service}"
install_file "${spacetimedb_service}" /etc/systemd/system/spacetimedb.service 0644
install_file "${api_service}" /etc/systemd/system/genarrative-api.service 0644
rm -f "${spacetimedb_service}" "${api_service}"
if [[ ! -f "${API_ENV_FILE}" ]]; then
echo "+ create ${API_ENV_FILE} from example"
if [[ "${DRY_RUN}" != "true" ]]; then
render_api_env_example >"${API_ENV_FILE}"
chmod 0600 "${API_ENV_FILE}"
chown root:root "${API_ENV_FILE}"
fi
else
echo "[server-provision] 已存在环境文件,保留不覆盖: ${API_ENV_FILE}"
fi
if [[ "${INSTALL_NGINX_CONFIG}" == "true" ]]; then
run_cmd mkdir -p /etc/nginx/snippets /etc/nginx/conf.d
echo "+ render deploy/nginx/genarrative.conf -> /etc/nginx/conf.d/genarrative.conf"
if [[ "${DRY_RUN}" != "true" ]]; then
render_nginx_config >/etc/nginx/conf.d/genarrative.conf
chmod 0644 /etc/nginx/conf.d/genarrative.conf
fi
install_file deploy/nginx/snippets/genarrative-maintenance.conf /etc/nginx/snippets/genarrative-maintenance.conf 0644
run_cmd nginx -t
fi
run_cmd systemctl daemon-reload
if [[ "${ENABLE_SERVICES}" == "true" ]]; then
run_cmd systemctl enable spacetimedb.service genarrative-api.service
run_cmd systemctl restart spacetimedb.service
if [[ -x "${CURRENT_LINK}/api-server" ]]; then
run_cmd systemctl restart genarrative-api.service
else
echo "[server-provision] 尚未发现 ${CURRENT_LINK}/api-server跳过 api-server 首次启动。后续 API deploy 会重启服务。"
fi
fi
echo "[server-provision] 完成。若是首次初始化,请补齐 ${API_ENV_FILE} 的真实密钥后再启动 api-server。"
'
'''
}
}
}
post {
success {
echo "Server provision 完成: target=${params.DEPLOY_TARGET}, dryRun=${params.DRY_RUN}"
}
}
}