Files
Genarrative/scripts/deploy/pingora-realpath-canary-enable.sh
T
kdletters 123d4ada01 脚本化 Pingora realpath canary 启停
新增 realpath canary 启用和关闭脚本

将脚本纳入 release、deploy、Jenkins 和 current release 自审

补充脚本化 canary 文档和生产运维护栏

增加 realpath canary 启停自测和发布部署护栏
2026-06-17 23:45:58 +08:00

351 lines
11 KiB
Bash
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.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." >/dev/null 2>&1 && pwd)"
TEMPLATE_PATH="${GENARRATIVE_PINGORA_REALPATH_CANARY_TEMPLATE_PATH:-${REPO_ROOT}/deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf}"
TARGET_PATH="${GENARRATIVE_PINGORA_REALPATH_CANARY_TARGET_PATH:-/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf}"
PROBE_TOKEN="${GENARRATIVE_PINGORA_REALPATH_CANARY_PROBE_TOKEN:-${GENARRATIVE_PINGORA_GATEWAY_PROBE_TOKEN:-}}"
BASE_URL="${GENARRATIVE_PINGORA_REALPATH_CANARY_BASE_URL:-http://127.0.0.1:18083}"
HOST_HEADER="${GENARRATIVE_PINGORA_REALPATH_CANARY_HOST:-}"
NGINX_BINARY="${GENARRATIVE_NGINX_BINARY:-nginx}"
NGINX_SERVICE="${GENARRATIVE_NGINX_SERVICE:-nginx.service}"
SYSTEMCTL_BINARY="${GENARRATIVE_SYSTEMCTL_BINARY:-systemctl}"
LIVE_SCRIPT="${GENARRATIVE_PINGORA_REALPATH_CANARY_LIVE_SCRIPT:-${REPO_ROOT}/scripts/check-pingora-canary-live.mjs}"
APPLY="false"
RUN_LIVE="true"
STATUS_AFTER="true"
PLACEHOLDER="__GENARRATIVE_PINGORA_PROBE_TOKEN__"
usage() {
cat <<'EOF'
用法:
scripts/deploy/pingora-realpath-canary-enable.sh [--apply] [--probe-token <token>] [--host <host>] [--base-url <url>] [--template-path <path>] [--target-path <path>] [--nginx-binary <path|name>] [--nginx-service <unit>] [--systemctl-binary <path|name>] [--live-script <path>] [--skip-live] [--no-status]
说明:
启用 Nginx -> Pingora 真实路径 canary。脚本会把随包
deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf 渲染到
/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf。
默认是 dry-run,只打印计划;必须显式传 --apply 才会写 Nginx 配置。
--apply 会替换 probe token、写入目标文件、执行 nginx -t、reload nginx
然后运行 realpath canary live smoke。任一阶段失败都会恢复写入前的目标文件状态。
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--apply)
APPLY="true"
shift
;;
--probe-token)
PROBE_TOKEN="${2:-}"
if [[ -z "${PROBE_TOKEN}" ]]; then
echo "[pingora-realpath-canary-enable] --probe-token 缺少参数" >&2
exit 1
fi
shift 2
;;
--host)
HOST_HEADER="${2:-}"
if [[ -z "${HOST_HEADER}" ]]; then
echo "[pingora-realpath-canary-enable] --host 缺少参数" >&2
exit 1
fi
shift 2
;;
--base-url)
BASE_URL="${2:-}"
if [[ -z "${BASE_URL}" ]]; then
echo "[pingora-realpath-canary-enable] --base-url 缺少参数" >&2
exit 1
fi
shift 2
;;
--template-path)
TEMPLATE_PATH="${2:-}"
if [[ -z "${TEMPLATE_PATH}" ]]; then
echo "[pingora-realpath-canary-enable] --template-path 缺少参数" >&2
exit 1
fi
shift 2
;;
--target-path)
TARGET_PATH="${2:-}"
if [[ -z "${TARGET_PATH}" ]]; then
echo "[pingora-realpath-canary-enable] --target-path 缺少参数" >&2
exit 1
fi
shift 2
;;
--nginx-binary)
NGINX_BINARY="${2:-}"
if [[ -z "${NGINX_BINARY}" ]]; then
echo "[pingora-realpath-canary-enable] --nginx-binary 缺少参数" >&2
exit 1
fi
shift 2
;;
--nginx-service)
NGINX_SERVICE="${2:-}"
if [[ -z "${NGINX_SERVICE}" ]]; then
echo "[pingora-realpath-canary-enable] --nginx-service 缺少参数" >&2
exit 1
fi
shift 2
;;
--systemctl-binary)
SYSTEMCTL_BINARY="${2:-}"
if [[ -z "${SYSTEMCTL_BINARY}" ]]; then
echo "[pingora-realpath-canary-enable] --systemctl-binary 缺少参数" >&2
exit 1
fi
shift 2
;;
--live-script)
LIVE_SCRIPT="${2:-}"
if [[ -z "${LIVE_SCRIPT}" ]]; then
echo "[pingora-realpath-canary-enable] --live-script 缺少参数" >&2
exit 1
fi
shift 2
;;
--skip-live)
RUN_LIVE="false"
shift
;;
--no-status)
STATUS_AFTER="false"
shift
;;
*)
echo "[pingora-realpath-canary-enable] 未知参数: $1" >&2
usage >&2
exit 1
;;
esac
done
reject_control_characters() {
local label="$1"
local value="$2"
if [[ "${value}" == *$'\n'* || "${value}" == *$'\r'* ]]; then
echo "[pingora-realpath-canary-enable] ${label} 不能包含换行或 NUL 字符。" >&2
exit 1
fi
}
is_filesystem_root_path() {
local value="$1"
local without_slashes="${value//\//}"
[[ -n "${value}" && -z "${without_slashes}" ]]
}
require_absolute_path() {
local label="$1"
local value="$2"
if [[ "${value}" != /* ]]; then
echo "[pingora-realpath-canary-enable] ${label} 必须是绝对路径。" >&2
exit 1
fi
if is_filesystem_root_path "${value}"; then
echo "[pingora-realpath-canary-enable] ${label} 不能是文件系统根目录。" >&2
exit 1
fi
}
validate_command_or_absolute_path() {
local label="$1"
local value="$2"
if [[ "${value}" == */* ]]; then
require_absolute_path "${label}" "${value}"
fi
}
validate_host() {
local label="$1"
local value="$2"
if [[ -z "${value}" ]]; then
echo "[pingora-realpath-canary-enable] ${label} 必须提供。" >&2
exit 1
fi
if [[ "${value}" == http://* || "${value}" == https://* || "${value}" == */* || "${value}" == *\?* || "${value}" =~ [[:space:]] ]]; then
echo "[pingora-realpath-canary-enable] ${label} 必须是 Host 或 host:port,不能是 URL、路径、查询或包含空白。" >&2
exit 1
fi
}
validate_base_url() {
if [[ "${BASE_URL}" != http://* && "${BASE_URL}" != https://* ]]; then
echo "[pingora-realpath-canary-enable] --base-url 必须以 http:// 或 https:// 开头。" >&2
exit 1
fi
}
validate_probe_token() {
if [[ "${APPLY}" != "true" ]]; then
return
fi
if [[ -z "${PROBE_TOKEN}" ]]; then
echo "[pingora-realpath-canary-enable] --apply 必须提供 --probe-token 或 GENARRATIVE_PINGORA_REALPATH_CANARY_PROBE_TOKEN。" >&2
exit 1
fi
if [[ "${PROBE_TOKEN}" == "${PLACEHOLDER}" || "${#PROBE_TOKEN}" -lt 8 ]]; then
echo "[pingora-realpath-canary-enable] probe token 不能是占位值或过短。" >&2
exit 1
fi
}
escape_nginx_double_quoted() {
local value="$1"
value="${value//\\/\\\\}"
value="${value//\"/\\\"}"
printf '%s' "${value}"
}
render_template_to() {
local output_path="$1"
local content escaped_token
content="$(<"${TEMPLATE_PATH}")"
escaped_token="$(escape_nginx_double_quoted "${PROBE_TOKEN}")"
printf '%s' "${content//${PLACEHOLDER}/${escaped_token}}" >"${output_path}"
}
print_command() {
printf '+'
for arg in "$@"; do
printf ' %q' "${arg}"
done
printf '\n'
}
run_or_print() {
if [[ "${APPLY}" == "true" ]]; then
"$@"
else
print_command "$@"
fi
}
restore_previous() {
local backup_path="$1"
local had_target="$2"
if [[ "${had_target}" == "true" ]]; then
cp -p -- "${backup_path}" "${TARGET_PATH}"
else
rm -f -- "${TARGET_PATH}"
fi
}
rollback_after_failure() {
local backup_path="$1"
local had_target="$2"
local reason="$3"
echo "[pingora-realpath-canary-enable] ${reason},恢复写入前配置。" >&2
restore_previous "${backup_path}" "${had_target}"
"${NGINX_BINARY}" -t >/dev/null 2>&1 || true
"${SYSTEMCTL_BINARY}" reload "${NGINX_SERVICE}" >/dev/null 2>&1 || true
}
reject_control_characters "--template-path" "${TEMPLATE_PATH}"
reject_control_characters "--target-path" "${TARGET_PATH}"
reject_control_characters "--probe-token" "${PROBE_TOKEN}"
reject_control_characters "--base-url" "${BASE_URL}"
reject_control_characters "--host" "${HOST_HEADER}"
reject_control_characters "--nginx-binary" "${NGINX_BINARY}"
reject_control_characters "--nginx-service" "${NGINX_SERVICE}"
reject_control_characters "--systemctl-binary" "${SYSTEMCTL_BINARY}"
reject_control_characters "--live-script" "${LIVE_SCRIPT}"
require_absolute_path "--template-path" "${TEMPLATE_PATH}"
require_absolute_path "--target-path" "${TARGET_PATH}"
if [[ "${RUN_LIVE}" == "true" ]]; then
require_absolute_path "--live-script" "${LIVE_SCRIPT}"
fi
validate_command_or_absolute_path "--nginx-binary" "${NGINX_BINARY}"
validate_command_or_absolute_path "--systemctl-binary" "${SYSTEMCTL_BINARY}"
validate_host "--host" "${HOST_HEADER}"
validate_base_url
validate_probe_token
if [[ ! -f "${TEMPLATE_PATH}" ]]; then
echo "[pingora-realpath-canary-enable] 未找到 realpath canary 模板: ${TEMPLATE_PATH}" >&2
exit 1
fi
if [[ "${RUN_LIVE}" == "true" && ! -f "${LIVE_SCRIPT}" ]]; then
echo "[pingora-realpath-canary-enable] 未找到 realpath canary live smoke 脚本: ${LIVE_SCRIPT}" >&2
exit 1
fi
TARGET_DIR="$(dirname -- "${TARGET_PATH}")"
if [[ "${APPLY}" == "true" ]]; then
if [[ ! -d "${TARGET_DIR}" ]]; then
echo "[pingora-realpath-canary-enable] 目标目录不存在: ${TARGET_DIR}" >&2
exit 1
fi
if [[ -L "${TARGET_DIR}" || -L "${TARGET_PATH}" ]]; then
echo "[pingora-realpath-canary-enable] 目标目录或目标文件不能是符号链接。" >&2
exit 1
fi
fi
if [[ "${APPLY}" != "true" ]]; then
echo "[pingora-realpath-canary-enable] dry-run:将渲染 realpath canary 到 ${TARGET_PATH}probe token 输出已隐藏。"
print_command install -m 0644 "<rendered-realpath-canary>" "${TARGET_PATH}"
run_or_print "${NGINX_BINARY}" -t
run_or_print "${SYSTEMCTL_BINARY}" reload "${NGINX_SERVICE}"
if [[ "${RUN_LIVE}" == "true" ]]; then
run_or_print node -- "${LIVE_SCRIPT}" --realpath --base-url "${BASE_URL}" --host "${HOST_HEADER}"
fi
exit 0
fi
tmp_path="$(mktemp "${TARGET_DIR}/.genarrative-pingora-realpath-canary.XXXXXX")"
backup_path="$(mktemp "${TARGET_DIR}/.genarrative-pingora-realpath-canary.backup.XXXXXX")"
had_target="false"
cleanup_paths=("${tmp_path}" "${backup_path}")
trap 'rm -f -- "${cleanup_paths[@]}"' EXIT
render_template_to "${tmp_path}"
chmod 0644 "${tmp_path}"
if [[ -e "${TARGET_PATH}" ]]; then
if [[ ! -f "${TARGET_PATH}" ]]; then
echo "[pingora-realpath-canary-enable] 目标已存在但不是普通文件: ${TARGET_PATH}" >&2
exit 1
fi
cp -p -- "${TARGET_PATH}" "${backup_path}"
had_target="true"
fi
install -m 0644 "${tmp_path}" "${TARGET_PATH}"
if ! "${NGINX_BINARY}" -t; then
rollback_after_failure "${backup_path}" "${had_target}" "nginx -t 失败"
exit 1
fi
if ! "${SYSTEMCTL_BINARY}" reload "${NGINX_SERVICE}"; then
rollback_after_failure "${backup_path}" "${had_target}" "reload ${NGINX_SERVICE} 失败"
exit 1
fi
if [[ "${RUN_LIVE}" == "true" ]]; then
if ! node -- "${LIVE_SCRIPT}" --realpath --base-url "${BASE_URL}" --host "${HOST_HEADER}"; then
rollback_after_failure "${backup_path}" "${had_target}" "realpath canary live smoke 失败"
exit 1
fi
fi
if [[ "${STATUS_AFTER}" == "true" ]]; then
"${SYSTEMCTL_BINARY}" is-active "${NGINX_SERVICE}"
fi
echo "[pingora-realpath-canary-enable] 已启用 realpath canary: ${TARGET_PATH}"