Guard server provision nginx install
This commit is contained in:
@@ -350,12 +350,16 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
||||
- 创建 `/stdb`、`/opt/genarrative`、`/srv/genarrative`、`/etc/genarrative`、`/var/lib/genarrative/maintenance`。
|
||||
- 安装或更新 SpacetimeDB。
|
||||
- 安装 systemd unit。
|
||||
- 安装 Nginx 配置和维护模式 snippet。
|
||||
- 执行 `nginx -t`。
|
||||
- 可选安装 Nginx 配置和维护模式 snippet。
|
||||
- 安装 Nginx 配置时执行 `nginx -t`。
|
||||
- 启用并启动 `spacetimedb.service` 与 `genarrative-api.service`。
|
||||
|
||||
该流水线属于高风险操作,默认要求人工确认后执行。
|
||||
已落地的 Jenkinsfile 为 `jenkins/Jenkinsfile.production-server-provision`。该流水线默认 `DRY_RUN=true`,只打印将执行的初始化动作;真正写入系统用户、目录、systemd、Nginx 配置并启动服务时,必须设置 `DRY_RUN=false` 且勾选 `CONFIRM_PROVISION`。当 `DEPLOY_TARGET=release` 时,还必须勾选 `CONFIRM_RELEASE_DEPLOY_AGENT`,并通过 `linux && genarrative-release-deploy` 调度到独立 release 部署 agent。
|
||||
已落地的 Jenkinsfile 为 `jenkins/Jenkinsfile.production-server-provision`。该流水线默认 `DRY_RUN=true`,只打印将执行的初始化动作;真正写入系统用户、目录、systemd、环境文件并启动服务时,必须设置 `DRY_RUN=false` 且勾选 `CONFIRM_PROVISION`。当 `DEPLOY_TARGET=release` 时,还必须勾选 `CONFIRM_RELEASE_DEPLOY_AGENT`,并通过 `linux && genarrative-release-deploy` 调度到独立 release 部署 agent。
|
||||
|
||||
首次真实初始化默认保持 `INSTALL_NGINX_CONFIG=false`,先完成系统用户、目录、SpacetimeDB、systemd unit 与 `/etc/genarrative/api-server.env` 落盘。等真实域名确定,并且目标机已经存在 `/etc/letsencrypt/live/<SERVER_NAME>/fullchain.pem` 与 `/etc/letsencrypt/live/<SERVER_NAME>/privkey.pem` 后,再把 `SERVER_NAME` 从 `genarrative.example.com` 改成真实域名,并设置 `INSTALL_NGINX_CONFIG=true` 安装 Nginx HTTPS 配置。流水线会拒绝在真实初始化中用占位域名写入 Nginx 配置,也会在证书缺失时提前失败。
|
||||
|
||||
若误用占位域名执行过真实初始化,失败通常发生在 `nginx -t`,错误表现为找不到 `/etc/letsencrypt/live/genarrative.example.com/fullchain.pem` 或 `privkey.pem`。新版初始化在 `INSTALL_NGINX_CONFIG=false` 时会检测并禁用上一轮留下的占位域名 Nginx 配置,避免它继续影响后续 `nginx -t`。
|
||||
|
||||
### Web Build / Deploy
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ pipeline {
|
||||
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: 'INSTALL_NGINX_CONFIG', defaultValue: false, description: '安装 Nginx 配置并执行 nginx -t;首次初始化且证书未准备好时保持关闭')
|
||||
booleanParam(name: 'ENABLE_SERVICES', defaultValue: true, description: '启用并启动 spacetimedb 与 api-server systemd 服务')
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ pipeline {
|
||||
if (!params.SPACETIME_BIN_SOURCE?.trim()) {
|
||||
error('SPACETIME_BIN_SOURCE 不能为空。')
|
||||
}
|
||||
if (!params.DRY_RUN && params.INSTALL_NGINX_CONFIG && params.SERVER_NAME?.trim() == 'genarrative.example.com') {
|
||||
error('真实初始化安装 Nginx 配置时必须把 SERVER_NAME 改成真实域名,不能使用 genarrative.example.com 占位值。证书未准备好时请先保持 INSTALL_NGINX_CONFIG=false。')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,6 +127,98 @@ pipeline {
|
||||
deploy/env/api-server.env.example
|
||||
}
|
||||
|
||||
validate_nginx_tls() {
|
||||
local cert_dir="/etc/letsencrypt/live/${SERVER_NAME}"
|
||||
if [[ "${SERVER_NAME}" == "genarrative.example.com" ]]; then
|
||||
echo "[server-provision] SERVER_NAME 仍是占位域名,拒绝写入 Nginx HTTPS 配置。请填写真实域名,或先设置 INSTALL_NGINX_CONFIG=false。" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "${cert_dir}/fullchain.pem" || ! -f "${cert_dir}/privkey.pem" ]]; then
|
||||
echo "[server-provision] 未找到 Nginx HTTPS 证书: ${cert_dir}/fullchain.pem 或 ${cert_dir}/privkey.pem" >&2
|
||||
echo "[server-provision] 请先完成证书申请,或首次初始化时设置 INSTALL_NGINX_CONFIG=false,避免写入无法通过 nginx -t 的配置。" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_nginx_config_with_rollback() {
|
||||
local config_target="/etc/nginx/conf.d/genarrative.conf"
|
||||
local snippet_target="/etc/nginx/snippets/genarrative-maintenance.conf"
|
||||
local rendered_config rendered_snippet config_backup snippet_backup
|
||||
local had_config="false"
|
||||
local had_snippet="false"
|
||||
|
||||
run_cmd mkdir -p /etc/nginx/snippets /etc/nginx/conf.d
|
||||
echo "+ render deploy/nginx/genarrative.conf -> ${config_target}"
|
||||
echo "+ install -m 0644 deploy/nginx/snippets/genarrative-maintenance.conf ${snippet_target}"
|
||||
|
||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||
echo "+ nginx -t"
|
||||
return
|
||||
fi
|
||||
|
||||
validate_nginx_tls
|
||||
rendered_config="$(mktemp)"
|
||||
rendered_snippet="$(mktemp)"
|
||||
config_backup="$(mktemp)"
|
||||
snippet_backup="$(mktemp)"
|
||||
render_nginx_config >"${rendered_config}"
|
||||
cp deploy/nginx/snippets/genarrative-maintenance.conf "${rendered_snippet}"
|
||||
|
||||
if [[ -f "${config_target}" ]]; then
|
||||
cp -p "${config_target}" "${config_backup}"
|
||||
had_config="true"
|
||||
fi
|
||||
if [[ -f "${snippet_target}" ]]; then
|
||||
cp -p "${snippet_target}" "${snippet_backup}"
|
||||
had_snippet="true"
|
||||
fi
|
||||
|
||||
install -m 0644 "${rendered_config}" "${config_target}"
|
||||
install -m 0644 "${rendered_snippet}" "${snippet_target}"
|
||||
|
||||
if ! nginx -t; then
|
||||
echo "[server-provision] nginx -t 失败,恢复写入前的 Nginx 配置。" >&2
|
||||
if [[ "${had_config}" == "true" ]]; then
|
||||
cp -p "${config_backup}" "${config_target}"
|
||||
else
|
||||
rm -f "${config_target}"
|
||||
fi
|
||||
if [[ "${had_snippet}" == "true" ]]; then
|
||||
cp -p "${snippet_backup}" "${snippet_target}"
|
||||
else
|
||||
rm -f "${snippet_target}"
|
||||
fi
|
||||
rm -f "${rendered_config}" "${rendered_snippet}" "${config_backup}" "${snippet_backup}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "${rendered_config}" "${rendered_snippet}" "${config_backup}" "${snippet_backup}"
|
||||
}
|
||||
|
||||
cleanup_placeholder_nginx_config() {
|
||||
local config_target="/etc/nginx/conf.d/genarrative.conf"
|
||||
local disabled_target
|
||||
|
||||
if [[ ! -f "${config_target}" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if ! grep -q "/etc/letsencrypt/live/genarrative.example.com/" "${config_target}"; then
|
||||
return
|
||||
fi
|
||||
|
||||
disabled_target="${config_target}.disabled-placeholder-$(date +%Y%m%d%H%M%S)"
|
||||
echo "[server-provision] 发现上一轮初始化留下的占位域名 Nginx 配置,禁用: ${config_target} -> ${disabled_target}"
|
||||
if [[ "${DRY_RUN}" != "true" ]]; then
|
||||
mv "${config_target}" "${disabled_target}"
|
||||
if command -v nginx >/dev/null 2>&1; then
|
||||
if ! nginx -t; then
|
||||
echo "[server-provision] 占位配置已禁用,但 nginx -t 仍失败;请检查其他 Nginx 配置。" >&2
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
escape_sed_replacement() {
|
||||
printf "%s" "$1" | sed "s/[&|]/\\\\&/g"
|
||||
}
|
||||
@@ -205,14 +300,9 @@ pipeline {
|
||||
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
|
||||
install_nginx_config_with_rollback
|
||||
else
|
||||
cleanup_placeholder_nginx_config
|
||||
fi
|
||||
|
||||
run_cmd systemctl daemon-reload
|
||||
|
||||
Reference in New Issue
Block a user