Add production Jenkins release pipelines
This commit is contained in:
12
scripts/deploy/maintenance-off.sh
Normal file
12
scripts/deploy/maintenance-off.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MAINTENANCE_FILE="${GENARRATIVE_MAINTENANCE_FILE:-/var/lib/genarrative/maintenance/enabled}"
|
||||
|
||||
if [[ -f "${MAINTENANCE_FILE}" ]]; then
|
||||
rm -f "${MAINTENANCE_FILE}"
|
||||
echo "[maintenance] 已退出维护模式: ${MAINTENANCE_FILE}"
|
||||
else
|
||||
echo "[maintenance] 当前未处于维护模式: ${MAINTENANCE_FILE}"
|
||||
fi
|
||||
15
scripts/deploy/maintenance-on.sh
Normal file
15
scripts/deploy/maintenance-on.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MAINTENANCE_FILE="${GENARRATIVE_MAINTENANCE_FILE:-/var/lib/genarrative/maintenance/enabled}"
|
||||
REASON="${*:-manual}"
|
||||
|
||||
mkdir -p "$(dirname "${MAINTENANCE_FILE}")"
|
||||
{
|
||||
printf "enabled_at=%s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
printf "reason=%s\n" "${REASON}"
|
||||
} >"${MAINTENANCE_FILE}"
|
||||
|
||||
chmod 0644 "${MAINTENANCE_FILE}"
|
||||
echo "[maintenance] 已进入维护模式: ${MAINTENANCE_FILE}"
|
||||
12
scripts/deploy/maintenance-status.sh
Normal file
12
scripts/deploy/maintenance-status.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MAINTENANCE_FILE="${GENARRATIVE_MAINTENANCE_FILE:-/var/lib/genarrative/maintenance/enabled}"
|
||||
|
||||
if [[ -f "${MAINTENANCE_FILE}" ]]; then
|
||||
echo "[maintenance] enabled"
|
||||
cat "${MAINTENANCE_FILE}"
|
||||
else
|
||||
echo "[maintenance] disabled"
|
||||
fi
|
||||
138
scripts/deploy/production-api-deploy.sh
Normal file
138
scripts/deploy/production-api-deploy.sh
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
./scripts/deploy/production-api-deploy.sh --source-dir build/<version> [--version <version>] [--release-root /opt/genarrative/releases] [--current-link /opt/genarrative/current] [--service genarrative-api.service] [--health-url http://127.0.0.1:8082/healthz]
|
||||
|
||||
说明:
|
||||
进入维护模式,校验并发布 api-server 单文件,更新 current 链接,重启 systemd 服务并执行 healthz 检查。
|
||||
失败时保留维护模式。
|
||||
EOF
|
||||
}
|
||||
|
||||
require_argument() {
|
||||
local value="$1"
|
||||
local label="$2"
|
||||
|
||||
if [[ -z "${value}" ]]; then
|
||||
echo "[production-api-deploy] 缺少参数: ${label}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SOURCE_DIR=""
|
||||
VERSION=""
|
||||
RELEASE_ROOT="/opt/genarrative/releases"
|
||||
CURRENT_LINK="/opt/genarrative/current"
|
||||
SERVICE_NAME="genarrative-api.service"
|
||||
HEALTH_URL="http://127.0.0.1:8082/healthz"
|
||||
DEPLOY_COMPLETED=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--source-dir)
|
||||
SOURCE_DIR="${2:?缺少 --source-dir 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--version)
|
||||
VERSION="${2:?缺少 --version 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--release-root)
|
||||
RELEASE_ROOT="${2:?缺少 --release-root 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--current-link)
|
||||
CURRENT_LINK="${2:?缺少 --current-link 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--service)
|
||||
SERVICE_NAME="${2:?缺少 --service 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--health-url)
|
||||
HEALTH_URL="${2:?缺少 --health-url 的值}"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "[production-api-deploy] 未知参数: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_argument "${SOURCE_DIR}" "--source-dir"
|
||||
|
||||
if [[ ! -d "${SOURCE_DIR}" ]]; then
|
||||
echo "[production-api-deploy] 发布目录不存在: ${SOURCE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR="$(cd "${SOURCE_DIR}" && pwd)"
|
||||
VERSION="${VERSION:-$(basename "${SOURCE_DIR}")}"
|
||||
|
||||
if [[ ! "${VERSION}" =~ ^[0-9A-Za-z._-]+$ ]]; then
|
||||
echo "[production-api-deploy] --version 只能包含数字、字母、点、下划线和短横线: ${VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${SOURCE_DIR}/api-server" || ! -f "${SOURCE_DIR}/api-server.sha256" ]]; then
|
||||
echo "[production-api-deploy] 缺少 api-server 或 api-server.sha256: ${SOURCE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
on_exit() {
|
||||
local exit_code=$?
|
||||
if [[ "${exit_code}" -ne 0 && "${DEPLOY_COMPLETED}" -ne 1 ]]; then
|
||||
echo "[production-api-deploy] 部署失败,保持维护模式。" >&2
|
||||
fi
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
trap on_exit EXIT
|
||||
|
||||
"${SCRIPT_DIR}/maintenance-on.sh" "api deploy ${VERSION}"
|
||||
|
||||
echo "[production-api-deploy] 校验 api-server"
|
||||
(
|
||||
cd "${SOURCE_DIR}"
|
||||
sha256sum -c api-server.sha256
|
||||
)
|
||||
|
||||
RELEASE_DIR="${RELEASE_ROOT}/${VERSION}"
|
||||
mkdir -p "${RELEASE_DIR}"
|
||||
cp "${SOURCE_DIR}/api-server" "${RELEASE_DIR}/api-server"
|
||||
chmod +x "${RELEASE_DIR}/api-server"
|
||||
|
||||
if [[ -f "${SOURCE_DIR}/release-manifest.json" ]]; then
|
||||
cp "${SOURCE_DIR}/release-manifest.json" "${RELEASE_DIR}/release-manifest.api-server.json"
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "${CURRENT_LINK}")"
|
||||
ln -sfn "${RELEASE_DIR}" "${CURRENT_LINK}"
|
||||
|
||||
echo "[production-api-deploy] 重启服务: ${SERVICE_NAME}"
|
||||
systemctl restart "${SERVICE_NAME}"
|
||||
|
||||
echo "[production-api-deploy] 等待 healthz: ${HEALTH_URL}"
|
||||
for _ in {1..30}; do
|
||||
if curl -fsS "${HEALTH_URL}" >/dev/null; then
|
||||
"${SCRIPT_DIR}/maintenance-off.sh"
|
||||
DEPLOY_COMPLETED=1
|
||||
echo "[production-api-deploy] 完成: ${RELEASE_DIR}/api-server"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "[production-api-deploy] healthz 检查超时: ${HEALTH_URL}" >&2
|
||||
exit 1
|
||||
123
scripts/deploy/production-stdb-publish.sh
Normal file
123
scripts/deploy/production-stdb-publish.sh
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
./scripts/deploy/production-stdb-publish.sh --source-dir build/<version> --database <database> [--server local] [--clear-database]
|
||||
|
||||
说明:
|
||||
进入维护模式,校验 spacetime_module.wasm.sha256,并在生产实例本机执行 spacetime publish。
|
||||
失败时保留维护模式。
|
||||
EOF
|
||||
}
|
||||
|
||||
require_argument() {
|
||||
local value="$1"
|
||||
local label="$2"
|
||||
|
||||
if [[ -z "${value}" ]]; then
|
||||
echo "[production-stdb-publish] 缺少参数: ${label}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_spacetime_database_name() {
|
||||
local database="$1"
|
||||
|
||||
if [[ ! "${database}" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
|
||||
echo "[production-stdb-publish] --database 必须匹配 ^[a-z0-9]+(-[a-z0-9]+)*$: ${database}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SOURCE_DIR=""
|
||||
DATABASE=""
|
||||
SERVER_ALIAS="local"
|
||||
CLEAR_DATABASE=0
|
||||
DEPLOY_COMPLETED=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--source-dir)
|
||||
SOURCE_DIR="${2:?缺少 --source-dir 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--database)
|
||||
DATABASE="${2:?缺少 --database 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--server)
|
||||
SERVER_ALIAS="${2:?缺少 --server 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--clear-database)
|
||||
CLEAR_DATABASE=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "[production-stdb-publish] 未知参数: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_argument "${SOURCE_DIR}" "--source-dir"
|
||||
require_argument "${DATABASE}" "--database"
|
||||
validate_spacetime_database_name "${DATABASE}"
|
||||
|
||||
if [[ ! -d "${SOURCE_DIR}" ]]; then
|
||||
echo "[production-stdb-publish] 发布目录不存在: ${SOURCE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR="$(cd "${SOURCE_DIR}" && pwd)"
|
||||
|
||||
if [[ ! -f "${SOURCE_DIR}/spacetime_module.wasm" || ! -f "${SOURCE_DIR}/spacetime_module.wasm.sha256" ]]; then
|
||||
echo "[production-stdb-publish] 缺少 spacetime_module.wasm 或 spacetime_module.wasm.sha256: ${SOURCE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
on_exit() {
|
||||
local exit_code=$?
|
||||
if [[ "${exit_code}" -ne 0 && "${DEPLOY_COMPLETED}" -ne 1 ]]; then
|
||||
echo "[production-stdb-publish] 发布失败,保持维护模式。" >&2
|
||||
fi
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
trap on_exit EXIT
|
||||
|
||||
"${SCRIPT_DIR}/maintenance-on.sh" "spacetime module publish ${DATABASE}"
|
||||
|
||||
echo "[production-stdb-publish] 校验 wasm"
|
||||
(
|
||||
cd "${SOURCE_DIR}"
|
||||
sha256sum -c spacetime_module.wasm.sha256
|
||||
)
|
||||
|
||||
PUBLISH_ARGS=(
|
||||
publish
|
||||
"${DATABASE}"
|
||||
--server "${SERVER_ALIAS}"
|
||||
--bin-path "${SOURCE_DIR}/spacetime_module.wasm"
|
||||
--yes
|
||||
)
|
||||
|
||||
if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then
|
||||
PUBLISH_ARGS+=(--clear-database)
|
||||
fi
|
||||
|
||||
echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_ALIAS}"
|
||||
spacetime "${PUBLISH_ARGS[@]}"
|
||||
|
||||
"${SCRIPT_DIR}/maintenance-off.sh"
|
||||
DEPLOY_COMPLETED=1
|
||||
echo "[production-stdb-publish] 完成"
|
||||
123
scripts/deploy/production-web-deploy.sh
Normal file
123
scripts/deploy/production-web-deploy.sh
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
./scripts/deploy/production-web-deploy.sh --source-dir build/<version> [--version <version>] [--release-root /opt/genarrative/releases] [--web-link /srv/genarrative/web] [--current-link /opt/genarrative/current]
|
||||
|
||||
说明:
|
||||
校验 web.tar.gz.sha256 后,把 web.tar.gz 解压到 /opt/genarrative/releases/<version>/web,
|
||||
并更新 /srv/genarrative/web 软链接。如果同版本目录已存在 api-server,则同步更新 /opt/genarrative/current。
|
||||
EOF
|
||||
}
|
||||
|
||||
require_argument() {
|
||||
local value="$1"
|
||||
local label="$2"
|
||||
|
||||
if [[ -z "${value}" ]]; then
|
||||
echo "[production-web-deploy] 缺少参数: ${label}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
SOURCE_DIR=""
|
||||
VERSION=""
|
||||
RELEASE_ROOT="/opt/genarrative/releases"
|
||||
CURRENT_LINK="/opt/genarrative/current"
|
||||
WEB_LINK="/srv/genarrative/web"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--source-dir)
|
||||
SOURCE_DIR="${2:?缺少 --source-dir 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--version)
|
||||
VERSION="${2:?缺少 --version 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--release-root)
|
||||
RELEASE_ROOT="${2:?缺少 --release-root 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--current-link)
|
||||
CURRENT_LINK="${2:?缺少 --current-link 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--web-link)
|
||||
WEB_LINK="${2:?缺少 --web-link 的值}"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "[production-web-deploy] 未知参数: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_argument "${SOURCE_DIR}" "--source-dir"
|
||||
|
||||
if [[ ! -d "${SOURCE_DIR}" ]]; then
|
||||
echo "[production-web-deploy] 发布目录不存在: ${SOURCE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR="$(cd "${SOURCE_DIR}" && pwd)"
|
||||
VERSION="${VERSION:-$(basename "${SOURCE_DIR}")}"
|
||||
|
||||
if [[ ! "${VERSION}" =~ ^[0-9A-Za-z._-]+$ ]]; then
|
||||
echo "[production-web-deploy] --version 只能包含数字、字母、点、下划线和短横线: ${VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${SOURCE_DIR}/web.tar.gz" || ! -f "${SOURCE_DIR}/web.tar.gz.sha256" ]]; then
|
||||
echo "[production-web-deploy] 缺少 web.tar.gz 或 web.tar.gz.sha256: ${SOURCE_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[production-web-deploy] 校验 Web 压缩包"
|
||||
(
|
||||
cd "${SOURCE_DIR}"
|
||||
sha256sum -c web.tar.gz.sha256
|
||||
)
|
||||
|
||||
RELEASE_DIR="${RELEASE_ROOT}/${VERSION}"
|
||||
WEB_TARGET="${RELEASE_DIR}/web"
|
||||
mkdir -p "${RELEASE_DIR}"
|
||||
rm -rf "${WEB_TARGET}"
|
||||
|
||||
echo "[production-web-deploy] 解压 Web 到: ${WEB_TARGET}"
|
||||
tar -xzf "${SOURCE_DIR}/web.tar.gz" -C "${RELEASE_DIR}"
|
||||
test -d "${WEB_TARGET}"
|
||||
|
||||
if [[ -f "${SOURCE_DIR}/release-manifest.json" ]]; then
|
||||
cp "${SOURCE_DIR}/release-manifest.json" "${RELEASE_DIR}/release-manifest.web.json"
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "${CURRENT_LINK}")" "$(dirname "${WEB_LINK}")"
|
||||
ln -sfn "${WEB_TARGET}" "${WEB_LINK}"
|
||||
if [[ -x "${RELEASE_DIR}/api-server" ]]; then
|
||||
ln -sfn "${RELEASE_DIR}" "${CURRENT_LINK}"
|
||||
else
|
||||
echo "[production-web-deploy] ${RELEASE_DIR}/api-server 不存在,仅更新 Web 软链接,保持 current 不变。"
|
||||
fi
|
||||
|
||||
if command -v nginx >/dev/null 2>&1; then
|
||||
echo "[production-web-deploy] nginx -t"
|
||||
nginx -t
|
||||
fi
|
||||
|
||||
if [[ ! -f "${WEB_TARGET}/index.html" ]]; then
|
||||
echo "[production-web-deploy] Web smoke test 失败,缺少 index.html: ${WEB_TARGET}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[production-web-deploy] 完成: ${WEB_TARGET}"
|
||||
Reference in New Issue
Block a user