123d4ada01
新增 realpath canary 启用和关闭脚本 将脚本纳入 release、deploy、Jenkins 和 current release 自审 补充脚本化 canary 文档和生产运维护栏 增加 realpath canary 启停自测和发布部署护栏
351 lines
11 KiB
Bash
351 lines
11 KiB
Bash
#!/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}"
|