199 lines
5.5 KiB
Bash
199 lines
5.5 KiB
Bash
#!/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-env-file /etc/genarrative/api-server.env] [--database genarrative-prod] [--spacetime-server-url http://127.0.0.1:3101]
|
||
|
||
说明:
|
||
进入维护模式,校验并发布 api-server 单文件,更新 current 链接,重启 systemd 服务并执行 healthz 检查。
|
||
若传入 --database,会在重启前把 GENARRATIVE_SPACETIME_DATABASE 写入 api-server 环境文件,避免服务继续读取旧库。
|
||
失败时保留维护模式。
|
||
EOF
|
||
}
|
||
|
||
require_argument() {
|
||
local value="$1"
|
||
local label="$2"
|
||
|
||
if [[ -z "${value}" ]]; then
|
||
echo "[production-api-deploy] 缺少参数: ${label}" >&2
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
validate_spacetime_database_name() {
|
||
local database="$1"
|
||
|
||
if [[ ! "${database}" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
|
||
echo "[production-api-deploy] --database 必须匹配 SpacetimeDB 数据库名规则 ^[a-z0-9]+(-[a-z0-9]+)*$,只能使用小写字母、数字,并用单个短横线分隔: ${database}" >&2
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
write_env_value() {
|
||
local file_path="$1"
|
||
local key="$2"
|
||
local value="$3"
|
||
local temp_path
|
||
|
||
mkdir -p "$(dirname "${file_path}")"
|
||
touch "${file_path}"
|
||
temp_path="$(mktemp)"
|
||
|
||
if grep -qE "^${key}=" "${file_path}"; then
|
||
sed -E "s|^${key}=.*|${key}=${value}|" "${file_path}" >"${temp_path}"
|
||
else
|
||
cp "${file_path}" "${temp_path}"
|
||
printf "%s=%s\n" "${key}" "${value}" >>"${temp_path}"
|
||
fi
|
||
|
||
cat "${temp_path}" >"${file_path}"
|
||
rm -f "${temp_path}"
|
||
}
|
||
|
||
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"
|
||
API_ENV_FILE="/etc/genarrative/api-server.env"
|
||
DATABASE=""
|
||
SPACETIME_SERVER_URL=""
|
||
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
|
||
;;
|
||
--api-env-file)
|
||
API_ENV_FILE="${2:?缺少 --api-env-file 的值}"
|
||
shift 2
|
||
;;
|
||
--database)
|
||
DATABASE="${2:?缺少 --database 的值}"
|
||
shift 2
|
||
;;
|
||
--spacetime-server-url)
|
||
SPACETIME_SERVER_URL="${2:?缺少 --spacetime-server-url 的值}"
|
||
shift 2
|
||
;;
|
||
*)
|
||
echo "[production-api-deploy] 未知参数: $1" >&2
|
||
usage >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
require_argument "${SOURCE_DIR}" "--source-dir"
|
||
|
||
if [[ -n "${DATABASE}" ]]; then
|
||
validate_spacetime_database_name "${DATABASE}"
|
||
fi
|
||
|
||
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
|
||
|
||
if [[ -n "${DATABASE}" ]]; then
|
||
echo "[production-api-deploy] 写入 api-server SpacetimeDB database: ${DATABASE} -> ${API_ENV_FILE}"
|
||
write_env_value "${API_ENV_FILE}" "GENARRATIVE_SPACETIME_DATABASE" "${DATABASE}"
|
||
fi
|
||
|
||
if [[ -n "${SPACETIME_SERVER_URL}" ]]; then
|
||
echo "[production-api-deploy] 写入 api-server SpacetimeDB server: ${SPACETIME_SERVER_URL} -> ${API_ENV_FILE}"
|
||
write_env_value "${API_ENV_FILE}" "GENARRATIVE_SPACETIME_SERVER_URL" "${SPACETIME_SERVER_URL}"
|
||
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
|