Files
Genarrative/scripts/deploy/pingora-direct-enable.sh
kdletters 75b85ee5a9 修正 Pingora 高端口重定向验收
direct live 支持独立 redirect base URL 预期

release readiness 和切换证据链透传 redirect base URL

直连启用脚本透传高端口 rehearsal 参数

补充 direct live guard、运维门禁和文档示例
2026-06-17 23:00:28 +08:00

923 lines
34 KiB
Bash
Raw Permalink 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)"
SERVICE_NAME="${GENARRATIVE_PINGORA_GATEWAY_SERVICE:-genarrative-pingora-gateway.service}"
DEFAULT_SERVICE_UNIT_PATH="${REPO_ROOT}/deploy/systemd/genarrative-pingora-gateway.service"
SERVICE_UNIT_PATH="${GENARRATIVE_PINGORA_DIRECT_SERVICE_UNIT_PATH:-${DEFAULT_SERVICE_UNIT_PATH}}"
DEFAULT_TEMPLATE_PATH="${REPO_ROOT}/deploy/systemd/genarrative-pingora-gateway-direct-entry.conf"
TEMPLATE_PATH="${GENARRATIVE_PINGORA_DIRECT_TEMPLATE_PATH:-${DEFAULT_TEMPLATE_PATH}}"
DROPIN_PATH="${GENARRATIVE_PINGORA_DIRECT_DROPIN_PATH:-/etc/systemd/system/genarrative-pingora-gateway.service.d/direct-entry.conf}"
PREFLIGHT_SCRIPT="${GENARRATIVE_PINGORA_DIRECT_PREFLIGHT_SCRIPT:-${REPO_ROOT}/scripts/check-pingora-direct-preflight.mjs}"
PREFLIGHT_ENV_FILE="${GENARRATIVE_PINGORA_DIRECT_PREFLIGHT_ENV_FILE:-}"
CURRENT_RELEASE_AUDIT_SCRIPT="${GENARRATIVE_PINGORA_DIRECT_CURRENT_RELEASE_AUDIT_SCRIPT:-${REPO_ROOT}/scripts/ops/pingora-current-release-audit.mjs}"
CURRENT_RELEASE_ROOT="${GENARRATIVE_PINGORA_DIRECT_CURRENT_RELEASE_ROOT:-${REPO_ROOT}}"
CURRENT_RELEASE_AUDIT_TIMEOUT_MS="${GENARRATIVE_PINGORA_DIRECT_CURRENT_RELEASE_AUDIT_TIMEOUT_MS:-}"
PREFLIGHT_SYSTEMD="false"
PREFLIGHT_CHECK_CERT_READABLE="false"
PREFLIGHT_CHECK_SERVICE_ENV_FILE="false"
PREFLIGHT_CHECK_SERVICE_USER_CERT_READABLE="false"
PREFLIGHT_CHECK_SERVICE_BINARY_EXECUTABLE="false"
PREFLIGHT_CHECK_PORTS_FREE="false"
DIRECT_LIVE_SCRIPT="${GENARRATIVE_PINGORA_DIRECT_LIVE_SCRIPT:-${REPO_ROOT}/scripts/check-pingora-direct-live.mjs}"
DIRECT_HTTPS_BASE_URL="${GENARRATIVE_PINGORA_DIRECT_HTTPS_BASE_URL:-}"
DIRECT_HTTP_BASE_URL="${GENARRATIVE_PINGORA_DIRECT_HTTP_BASE_URL:-}"
DIRECT_HOST="${GENARRATIVE_PINGORA_DIRECT_HOST:-}"
DIRECT_REDIRECT_HOST="${GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST:-}"
DIRECT_REDIRECT_BASE_URL="${GENARRATIVE_PINGORA_DIRECT_REDIRECT_BASE_URL:-}"
DIRECT_PROBE_TOKEN="${GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN:-}"
DIRECT_PINGORA_ACCESS_LOG="${GENARRATIVE_PINGORA_DIRECT_PINGORA_ACCESS_LOG:-}"
DIRECT_ACCESS_LOG_SINCE_LINES="${GENARRATIVE_PINGORA_DIRECT_ACCESS_LOG_SINCE_LINES:-}"
DIRECT_SPACETIME_DATABASE="${GENARRATIVE_PINGORA_DIRECT_SPACETIME_DATABASE:-}"
DIRECT_TIMEOUT_MS="${GENARRATIVE_PINGORA_DIRECT_TIMEOUT_MS:-}"
APPLY="false"
STATUS_AFTER="true"
VERIFY_SYSTEMD_AFTER="true"
usage() {
cat <<'EOF'
用法:
scripts/deploy/pingora-direct-enable.sh [--apply] [--preflight-env-file <path>] [--preflight-systemd] [--preflight-check-cert-readable] [--preflight-check-service-env-file] [--preflight-check-service-user-cert-readable] [--preflight-check-service-binary-executable] [--preflight-check-ports-free]
[--direct-https-base-url <url>] [--direct-http-base-url <url>] [--direct-host <host>] [--direct-redirect-host <host>] [--direct-redirect-base-url <url>]
[--direct-spacetime-database <name>] [--direct-probe-token <token>] [--direct-pingora-access-log <path>] [--direct-access-log-since-lines <count>] [--direct-timeout-ms <ms>] [--direct-live-script <path>] [--no-postcheck]
[--current-release-audit-script <path>] [--current-release-root <path>] [--current-release-audit-timeout-ms <ms>]
[--service <name>] [--service-unit-path <path>] [--template-path <path>] [--dropin-path <path>]
说明:
启用 Pingora 直连低端口入口:把已准备好的 direct-entry systemd drop-in 模板安装到
genarrative-pingora-gateway.service.d/direct-entry.conf,执行 daemon-reload
重启 genarrative-pingora-gateway.service,让服务具备绑定 80/443 的最小 capability。
默认是 dry-run,只打印将执行的命令;必须显式传 --apply 才会修改系统状态。
该脚本不写入 TLS env、不复制证书、不停止 Nginx--apply 会先执行 current release 自包含自审,
确认发布包已携带 Pingora 网关且 systemd ExecStart 指向随包二进制;--apply 时必须提供 --preflight-env-file、
--preflight-check-cert-readable、--preflight-check-service-env-file、--preflight-check-service-user-cert-readable、--preflight-check-service-binary-executable、--preflight-check-ports-free 和 direct live smoke 参数,
先通过 direct preflight,安装并重启 Pingora,再通过 direct live smoke 后才视为启用成功。
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--apply)
APPLY="true"
shift
;;
--no-status)
STATUS_AFTER="false"
shift
;;
--no-postcheck)
VERIFY_SYSTEMD_AFTER="false"
shift
;;
--service)
SERVICE_NAME="${2:-}"
if [[ -z "${SERVICE_NAME}" ]]; then
echo "[pingora-direct-enable] --service 缺少参数" >&2
exit 1
fi
shift 2
;;
--service-unit-path)
SERVICE_UNIT_PATH="${2:-}"
if [[ -z "${SERVICE_UNIT_PATH}" ]]; then
echo "[pingora-direct-enable] --service-unit-path 缺少参数" >&2
exit 1
fi
shift 2
;;
--template-path)
TEMPLATE_PATH="${2:-}"
if [[ -z "${TEMPLATE_PATH}" ]]; then
echo "[pingora-direct-enable] --template-path 缺少参数" >&2
exit 1
fi
shift 2
;;
--dropin-path)
DROPIN_PATH="${2:-}"
if [[ -z "${DROPIN_PATH}" ]]; then
echo "[pingora-direct-enable] --dropin-path 缺少参数" >&2
exit 1
fi
shift 2
;;
--preflight-script)
PREFLIGHT_SCRIPT="${2:-}"
if [[ -z "${PREFLIGHT_SCRIPT}" ]]; then
echo "[pingora-direct-enable] --preflight-script 缺少参数" >&2
exit 1
fi
shift 2
;;
--preflight-env-file)
PREFLIGHT_ENV_FILE="${2:-}"
if [[ -z "${PREFLIGHT_ENV_FILE}" ]]; then
echo "[pingora-direct-enable] --preflight-env-file 缺少参数" >&2
exit 1
fi
shift 2
;;
--current-release-audit-script)
CURRENT_RELEASE_AUDIT_SCRIPT="${2:-}"
if [[ -z "${CURRENT_RELEASE_AUDIT_SCRIPT}" ]]; then
echo "[pingora-direct-enable] --current-release-audit-script 缺少参数" >&2
exit 1
fi
shift 2
;;
--current-release-root)
CURRENT_RELEASE_ROOT="${2:-}"
if [[ -z "${CURRENT_RELEASE_ROOT}" ]]; then
echo "[pingora-direct-enable] --current-release-root 缺少参数" >&2
exit 1
fi
shift 2
;;
--current-release-audit-timeout-ms)
CURRENT_RELEASE_AUDIT_TIMEOUT_MS="${2:-}"
if [[ -z "${CURRENT_RELEASE_AUDIT_TIMEOUT_MS}" ]]; then
echo "[pingora-direct-enable] --current-release-audit-timeout-ms 缺少参数" >&2
exit 1
fi
shift 2
;;
--preflight-systemd)
PREFLIGHT_SYSTEMD="true"
shift
;;
--preflight-check-cert-readable)
PREFLIGHT_CHECK_CERT_READABLE="true"
shift
;;
--preflight-check-service-env-file)
PREFLIGHT_CHECK_SERVICE_ENV_FILE="true"
shift
;;
--preflight-check-service-user-cert-readable)
PREFLIGHT_CHECK_SERVICE_USER_CERT_READABLE="true"
shift
;;
--preflight-check-service-binary-executable)
PREFLIGHT_CHECK_SERVICE_BINARY_EXECUTABLE="true"
shift
;;
--preflight-check-ports-free)
PREFLIGHT_CHECK_PORTS_FREE="true"
shift
;;
--direct-live-script)
DIRECT_LIVE_SCRIPT="${2:-}"
if [[ -z "${DIRECT_LIVE_SCRIPT}" ]]; then
echo "[pingora-direct-enable] --direct-live-script 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-https-base-url)
DIRECT_HTTPS_BASE_URL="${2:-}"
if [[ -z "${DIRECT_HTTPS_BASE_URL}" ]]; then
echo "[pingora-direct-enable] --direct-https-base-url 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-http-base-url)
DIRECT_HTTP_BASE_URL="${2:-}"
if [[ -z "${DIRECT_HTTP_BASE_URL}" ]]; then
echo "[pingora-direct-enable] --direct-http-base-url 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-host)
DIRECT_HOST="${2:-}"
if [[ -z "${DIRECT_HOST}" ]]; then
echo "[pingora-direct-enable] --direct-host 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-redirect-host)
DIRECT_REDIRECT_HOST="${2:-}"
if [[ -z "${DIRECT_REDIRECT_HOST}" ]]; then
echo "[pingora-direct-enable] --direct-redirect-host 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-redirect-base-url)
DIRECT_REDIRECT_BASE_URL="${2:-}"
if [[ -z "${DIRECT_REDIRECT_BASE_URL}" ]]; then
echo "[pingora-direct-enable] --direct-redirect-base-url 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-probe-token)
DIRECT_PROBE_TOKEN="${2:-}"
if [[ -z "${DIRECT_PROBE_TOKEN}" ]]; then
echo "[pingora-direct-enable] --direct-probe-token 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-pingora-access-log)
DIRECT_PINGORA_ACCESS_LOG="${2:-}"
if [[ -z "${DIRECT_PINGORA_ACCESS_LOG}" ]]; then
echo "[pingora-direct-enable] --direct-pingora-access-log 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-access-log-since-lines)
DIRECT_ACCESS_LOG_SINCE_LINES="${2:-}"
if [[ -z "${DIRECT_ACCESS_LOG_SINCE_LINES}" ]]; then
echo "[pingora-direct-enable] --direct-access-log-since-lines 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-spacetime-database)
DIRECT_SPACETIME_DATABASE="${2:-}"
if [[ -z "${DIRECT_SPACETIME_DATABASE}" ]]; then
echo "[pingora-direct-enable] --direct-spacetime-database 缺少参数" >&2
exit 1
fi
shift 2
;;
--direct-timeout-ms)
DIRECT_TIMEOUT_MS="${2:-}"
if [[ -z "${DIRECT_TIMEOUT_MS}" ]]; then
echo "[pingora-direct-enable] --direct-timeout-ms 缺少参数" >&2
exit 1
fi
shift 2
;;
*)
echo "[pingora-direct-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-direct-enable] ${label} 不能包含换行或 NUL 字符。" >&2
exit 1
fi
}
is_filesystem_root_path() {
local value="$1"
local without_slashes="${value//\//}"
[[ -n "${value}" && -z "${without_slashes}" ]]
}
reject_filesystem_root_path() {
local label="$1"
local value="$2"
if is_filesystem_root_path "${value}"; then
echo "[pingora-direct-enable] ${label} 不能是文件系统根目录。" >&2
exit 1
fi
}
reject_control_characters "--service" "${SERVICE_NAME}"
reject_control_characters "--service-unit-path" "${SERVICE_UNIT_PATH}"
reject_control_characters "--template-path" "${TEMPLATE_PATH}"
reject_control_characters "--dropin-path" "${DROPIN_PATH}"
reject_control_characters "--preflight-script" "${PREFLIGHT_SCRIPT}"
reject_control_characters "--preflight-env-file" "${PREFLIGHT_ENV_FILE}"
reject_control_characters "--current-release-audit-script" "${CURRENT_RELEASE_AUDIT_SCRIPT}"
reject_control_characters "--current-release-root" "${CURRENT_RELEASE_ROOT}"
reject_control_characters "--current-release-audit-timeout-ms" "${CURRENT_RELEASE_AUDIT_TIMEOUT_MS}"
reject_control_characters "--direct-live-script" "${DIRECT_LIVE_SCRIPT}"
reject_control_characters "--direct-https-base-url" "${DIRECT_HTTPS_BASE_URL}"
reject_control_characters "--direct-http-base-url" "${DIRECT_HTTP_BASE_URL}"
reject_control_characters "--direct-host" "${DIRECT_HOST}"
reject_control_characters "--direct-redirect-host" "${DIRECT_REDIRECT_HOST}"
reject_control_characters "--direct-redirect-base-url" "${DIRECT_REDIRECT_BASE_URL}"
reject_control_characters "--direct-probe-token" "${DIRECT_PROBE_TOKEN}"
reject_control_characters "--direct-pingora-access-log" "${DIRECT_PINGORA_ACCESS_LOG}"
reject_control_characters "--direct-access-log-since-lines" "${DIRECT_ACCESS_LOG_SINCE_LINES}"
reject_control_characters "--direct-spacetime-database" "${DIRECT_SPACETIME_DATABASE}"
reject_control_characters "--direct-timeout-ms" "${DIRECT_TIMEOUT_MS}"
reject_filesystem_root_path "--service-unit-path" "${SERVICE_UNIT_PATH}"
reject_filesystem_root_path "--template-path" "${TEMPLATE_PATH}"
reject_filesystem_root_path "--dropin-path" "${DROPIN_PATH}"
reject_filesystem_root_path "--preflight-env-file" "${PREFLIGHT_ENV_FILE}"
reject_filesystem_root_path "--preflight-script" "${PREFLIGHT_SCRIPT}"
reject_filesystem_root_path "--current-release-audit-script" "${CURRENT_RELEASE_AUDIT_SCRIPT}"
reject_filesystem_root_path "--current-release-root" "${CURRENT_RELEASE_ROOT}"
reject_filesystem_root_path "--direct-live-script" "${DIRECT_LIVE_SCRIPT}"
reject_filesystem_root_path "--direct-pingora-access-log" "${DIRECT_PINGORA_ACCESS_LOG}"
if [[ "${TEMPLATE_PATH}" != /* ]]; then
echo "[pingora-direct-enable] --template-path 必须是绝对路径: ${TEMPLATE_PATH}" >&2
exit 1
fi
if [[ "${SERVICE_UNIT_PATH}" != /* ]]; then
echo "[pingora-direct-enable] --service-unit-path 必须是绝对路径: ${SERVICE_UNIT_PATH}" >&2
exit 1
fi
if [[ "${DROPIN_PATH}" != /* ]]; then
echo "[pingora-direct-enable] --dropin-path 必须是绝对路径: ${DROPIN_PATH}" >&2
exit 1
fi
if [[ -n "${PREFLIGHT_ENV_FILE}" && "${PREFLIGHT_ENV_FILE}" != /* ]]; then
echo "[pingora-direct-enable] --preflight-env-file 必须是绝对路径: ${PREFLIGHT_ENV_FILE}" >&2
exit 1
fi
if [[ "${PREFLIGHT_SCRIPT}" != /* ]]; then
echo "[pingora-direct-enable] --preflight-script 必须是绝对路径: ${PREFLIGHT_SCRIPT}" >&2
exit 1
fi
if [[ "${CURRENT_RELEASE_AUDIT_SCRIPT}" != /* ]]; then
echo "[pingora-direct-enable] --current-release-audit-script 必须是绝对路径: ${CURRENT_RELEASE_AUDIT_SCRIPT}" >&2
exit 1
fi
if [[ "${CURRENT_RELEASE_ROOT}" != /* ]]; then
echo "[pingora-direct-enable] --current-release-root 必须是绝对路径: ${CURRENT_RELEASE_ROOT}" >&2
exit 1
fi
if [[ "${DIRECT_LIVE_SCRIPT}" != /* ]]; then
echo "[pingora-direct-enable] --direct-live-script 必须是绝对路径: ${DIRECT_LIVE_SCRIPT}" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && -z "${PREFLIGHT_ENV_FILE}" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --preflight-env-file,避免跳过直连 env / 证书预检。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && "${PREFLIGHT_CHECK_CERT_READABLE}" != "true" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --preflight-check-cert-readable,避免跳过 TLS 证书和私钥可读预检。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && "${PREFLIGHT_CHECK_SERVICE_ENV_FILE}" != "true" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --preflight-check-service-env-file,避免检查的 env 文件和 systemd service 实际读取的 EnvironmentFile 不一致。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && "${PREFLIGHT_CHECK_SERVICE_USER_CERT_READABLE}" != "true" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --preflight-check-service-user-cert-readable,避免跳过 systemd 服务用户读取 TLS 证书和私钥预检。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && "${PREFLIGHT_CHECK_SERVICE_BINARY_EXECUTABLE}" != "true" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --preflight-check-service-binary-executable,避免 current release 缺少可执行 pingora-gateway 时才在重启阶段失败。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && "${PREFLIGHT_CHECK_PORTS_FREE}" != "true" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --preflight-check-ports-free,避免 Nginx 或其它进程仍占用 80/443。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && -z "${DIRECT_HTTPS_BASE_URL}" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --direct-https-base-url,启用后验证 Pingora HTTPS 直连入口。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && -z "${DIRECT_HTTP_BASE_URL}" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --direct-http-base-url,启用后验证 HTTP redirect / ACME 入口。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && -z "${DIRECT_HOST}" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --direct-host,启用后用正式域名 Host/SNI 验证直连入口。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && -z "${DIRECT_REDIRECT_HOST}" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --direct-redirect-host,启用后验证 HTTP redirect Location host。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && -z "${DIRECT_PINGORA_ACCESS_LOG}" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --direct-pingora-access-log,启用后验证直连请求已写入 Pingora access log。" >&2
exit 1
fi
if [[ -n "${DIRECT_PINGORA_ACCESS_LOG}" && "${DIRECT_PINGORA_ACCESS_LOG}" != /* ]]; then
echo "[pingora-direct-enable] --direct-pingora-access-log 必须是绝对路径: ${DIRECT_PINGORA_ACCESS_LOG}" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && -z "${DIRECT_SPACETIME_DATABASE}" ]]; then
echo "[pingora-direct-enable] --apply 必须同时提供 --direct-spacetime-database,启用后验证目标库 WSS subscribe。" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && ! -f "${TEMPLATE_PATH}" ]]; then
echo "[pingora-direct-enable] direct-entry 模板不存在: ${TEMPLATE_PATH}" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && ! -f "${SERVICE_UNIT_PATH}" ]]; then
echo "[pingora-direct-enable] 主 service 模板不存在: ${SERVICE_UNIT_PATH}" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && ! -f "${CURRENT_RELEASE_AUDIT_SCRIPT}" ]]; then
echo "[pingora-direct-enable] current release 自审脚本不存在: ${CURRENT_RELEASE_AUDIT_SCRIPT}" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && ! -f "${PREFLIGHT_SCRIPT}" ]]; then
echo "[pingora-direct-enable] direct preflight 脚本不存在: ${PREFLIGHT_SCRIPT}" >&2
exit 1
fi
if [[ "${APPLY}" == "true" && ! -f "${DIRECT_LIVE_SCRIPT}" ]]; then
echo "[pingora-direct-enable] direct live smoke 脚本不存在: ${DIRECT_LIVE_SCRIPT}" >&2
exit 1
fi
if [[ "${APPLY}" == "true" ]]; then
DROPIN_DIR="$(dirname "${DROPIN_PATH}")"
if [[ -L "${DROPIN_DIR}" ]]; then
echo "[pingora-direct-enable] drop-in 目录不能是符号链接: ${DROPIN_DIR}" >&2
exit 1
fi
if [[ -e "${DROPIN_DIR}" && ! -d "${DROPIN_DIR}" ]]; then
echo "[pingora-direct-enable] drop-in 父路径必须是目录: ${DROPIN_DIR}" >&2
exit 1
fi
if [[ -L "${DROPIN_PATH}" ]]; then
echo "[pingora-direct-enable] drop-in 目标不能是符号链接: ${DROPIN_PATH}" >&2
exit 1
fi
if [[ -e "${DROPIN_PATH}" && ! -f "${DROPIN_PATH}" ]]; then
echo "[pingora-direct-enable] drop-in 目标已存在但不是普通文件: ${DROPIN_PATH}" >&2
exit 1
fi
fi
run_cmd() {
echo "+ $*"
if [[ "${APPLY}" == "true" ]]; then
"$@"
fi
}
print_redacted_command() {
local previous="" output=()
for arg in "$@"; do
if [[ "${previous}" == "--probe-token" || "${previous}" == "--direct-probe-token" ]]; then
output+=("<redacted>")
else
output+=("${arg}")
fi
previous="${arg}"
done
echo "+ ${output[*]}"
}
run_systemctl() {
if [[ "${APPLY}" != "true" ]]; then
run_cmd systemctl "$@"
return
fi
if command -v systemctl >/dev/null 2>&1; then
run_cmd systemctl "$@"
else
echo "[pingora-direct-enable] 未找到 systemctl,无法执行: systemctl $*" >&2
exit 1
fi
}
run_preflight() {
if [[ -z "${PREFLIGHT_ENV_FILE}" ]]; then
echo "[pingora-direct-enable] 未提供 --preflight-env-file,仅执行脚本 dry-run--apply 会强制要求预检。"
return
fi
preflight_args=(
"${PREFLIGHT_SCRIPT}"
--env-file "${PREFLIGHT_ENV_FILE}"
--require-live-env
)
if [[ "${PREFLIGHT_SYSTEMD}" == "true" ]]; then
preflight_args+=(--systemd-cat)
fi
if [[ "${PREFLIGHT_CHECK_CERT_READABLE}" == "true" ]]; then
preflight_args+=(--check-cert-readable)
fi
if [[ "${PREFLIGHT_CHECK_SERVICE_ENV_FILE}" == "true" ]]; then
preflight_args+=(--check-service-env-file)
fi
if [[ "${PREFLIGHT_CHECK_SERVICE_USER_CERT_READABLE}" == "true" ]]; then
preflight_args+=(--check-service-user-cert-readable)
fi
if [[ "${PREFLIGHT_CHECK_SERVICE_BINARY_EXECUTABLE}" == "true" ]]; then
preflight_args+=(--check-service-binary-executable)
fi
if [[ "${PREFLIGHT_CHECK_PORTS_FREE}" == "true" ]]; then
preflight_args+=(--check-ports-free)
fi
echo "+ node -- ${preflight_args[*]}"
node -- "${preflight_args[@]}"
}
run_current_release_audit() {
audit_args=(
"${CURRENT_RELEASE_AUDIT_SCRIPT}"
--release-root "${CURRENT_RELEASE_ROOT}"
--require-pingora-gateway
--systemd-show
--systemd-service "${SERVICE_NAME}"
)
if [[ -n "${CURRENT_RELEASE_AUDIT_TIMEOUT_MS}" ]]; then
audit_args+=(--timeout-ms "${CURRENT_RELEASE_AUDIT_TIMEOUT_MS}")
fi
echo "+ node -- ${audit_args[*]}"
if [[ "${APPLY}" != "true" ]]; then
echo "[pingora-direct-enable] dry-run--apply 前会先执行 current release 自包含自审。"
return
fi
node -- "${audit_args[@]}"
}
systemd_env_file_matches() {
local expected="$1"
local line value word normalized
while IFS= read -r line; do
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[[ "${line}" == EnvironmentFile=* ]] || continue
value="${line#EnvironmentFile=}"
[[ -n "${value}" ]] || continue
for word in ${value}; do
normalized="${word%\"}"
normalized="${normalized#\"}"
normalized="${normalized%\'}"
normalized="${normalized#\'}"
normalized="${normalized#-}"
if [[ "${normalized}" == "${expected}" ]]; then
return 0
fi
done
done <<<"${unit_content}"
return 1
}
read_expected_exec_start() {
local line value
if [[ ! -f "${SERVICE_UNIT_PATH}" ]]; then
return 1
fi
while IFS= read -r line; do
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[[ "${line}" == ExecStart=* ]] || continue
value="${line#ExecStart=}"
[[ -n "${value}" ]] || continue
printf '%s\n' "${value}"
return 0
done <"${SERVICE_UNIT_PATH}"
return 1
}
first_exec_word() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
if [[ "${value}" == \"* ]]; then
value="${value#\"}"
printf '%s\n' "${value%%\"*}"
return
fi
printf '%s\n' "${value%%[[:space:]]*}"
}
systemd_show_exec_start_matches() {
local expected_exec="$1"
local expected_binary actual_show actual_binary
expected_binary="$(first_exec_word "${expected_exec}")"
[[ -n "${expected_binary}" ]] || return 1
actual_show="$(systemctl show "${SERVICE_NAME}" --property=ExecStart --value --no-pager)"
actual_binary="${actual_show}"
if [[ "${actual_show}" == *"path="* ]]; then
actual_binary="${actual_show#*path=}"
actual_binary="${actual_binary%% ;*}"
elif [[ "${actual_show}" == ExecStart=* ]]; then
actual_binary="${actual_show#ExecStart=}"
actual_binary="$(first_exec_word "${actual_binary}")"
else
actual_binary="$(first_exec_word "${actual_binary}")"
fi
[[ "${actual_binary}" == "${expected_binary}" ]]
}
verify_systemd_exec_start() {
if [[ "${VERIFY_SYSTEMD_AFTER}" != "true" ]]; then
echo "[pingora-direct-enable] 已跳过 Pingora ExecStart 指向核验。"
return
fi
expected_exec_start="$(read_expected_exec_start || true)"
if [[ -z "${expected_exec_start}" ]]; then
echo "[pingora-direct-enable] 无法从主 service 模板读取 ExecStart: ${SERVICE_UNIT_PATH}" >&2
exit 1
fi
if [[ "${APPLY}" != "true" ]]; then
echo "+ systemctl show ${SERVICE_NAME} --property=ExecStart --value --no-pager"
echo "[pingora-direct-enable] dry-run--apply 后会核验 systemd ExecStart 指向主 service 模板中的 ${expected_exec_start}。"
return
fi
if ! command -v systemctl >/dev/null 2>&1; then
echo "[pingora-direct-enable] 未找到 systemctl,无法核验 Pingora ExecStart。" >&2
exit 1
fi
echo "+ systemctl show ${SERVICE_NAME} --property=ExecStart --value --no-pager"
if ! systemd_show_exec_start_matches "${expected_exec_start}"; then
echo "[pingora-direct-enable] systemctl show ExecStart 未指向主 service 模板中的 ${expected_exec_start},请先修正 ${SERVICE_NAME} 指向 current release 网关二进制。" >&2
exit 1
fi
}
verify_systemd_dropin() {
if [[ "${VERIFY_SYSTEMD_AFTER}" != "true" ]]; then
echo "[pingora-direct-enable] 已跳过 systemd drop-in 生效核验。"
return
fi
if [[ "${APPLY}" != "true" ]]; then
echo "+ systemctl cat ${SERVICE_NAME}"
echo "[pingora-direct-enable] dry-run--apply 后会校验 systemd 最终配置包含 CAP_NET_BIND_SERVICE。"
return
fi
if ! command -v systemctl >/dev/null 2>&1; then
echo "[pingora-direct-enable] 未找到 systemctl,无法核验 drop-in 是否生效。" >&2
exit 1
fi
echo "+ systemctl cat ${SERVICE_NAME}"
unit_content="$(systemctl cat "${SERVICE_NAME}")"
if [[ "${unit_content}" != *"AmbientCapabilities=CAP_NET_BIND_SERVICE"* ]]; then
echo "[pingora-direct-enable] systemctl cat 未显示 AmbientCapabilities=CAP_NET_BIND_SERVICEdirect-entry drop-in 未生效。" >&2
exit 1
fi
if [[ "${unit_content}" != *"CapabilityBoundingSet=CAP_NET_BIND_SERVICE"* ]]; then
echo "[pingora-direct-enable] systemctl cat 未显示 CapabilityBoundingSet=CAP_NET_BIND_SERVICEdirect-entry drop-in 未生效。" >&2
exit 1
fi
if [[ "${PREFLIGHT_CHECK_SERVICE_ENV_FILE}" == "true" && -n "${PREFLIGHT_ENV_FILE}" ]]; then
if ! systemd_env_file_matches "${PREFLIGHT_ENV_FILE}"; then
echo "[pingora-direct-enable] systemctl cat 未显示 EnvironmentFile=${PREFLIGHT_ENV_FILE}service 实际读取的 env 与 preflight env 不一致。" >&2
exit 1
fi
fi
}
verify_service_active_after_restart() {
if [[ "${VERIFY_SYSTEMD_AFTER}" != "true" ]]; then
echo "[pingora-direct-enable] 已跳过 Pingora 重启后状态核验。"
return
fi
if [[ "${APPLY}" != "true" ]]; then
echo "+ systemctl is-active ${SERVICE_NAME}"
echo "[pingora-direct-enable] dry-run--apply 后会校验 Pingora service 为 active。"
return
fi
if ! command -v systemctl >/dev/null 2>&1; then
echo "[pingora-direct-enable] 未找到 systemctl,无法核验 Pingora 重启后状态。" >&2
exit 1
fi
echo "+ systemctl is-active ${SERVICE_NAME}"
service_state="$(systemctl is-active "${SERVICE_NAME}")"
if [[ "${service_state}" != "active" ]]; then
echo "[pingora-direct-enable] Pingora 重启后状态不是 active: ${service_state}" >&2
exit 1
fi
}
run_direct_live_smoke_after_enable() {
if [[ -z "${DIRECT_HTTPS_BASE_URL}" ]]; then
echo "+ node ${DIRECT_LIVE_SCRIPT} --https-base-url <direct-https-base-url> --http-base-url <direct-http-base-url> --host <direct-host> --redirect-host <direct-redirect-host> --spacetime-database <database> --require-wss-upgrade"
echo "[pingora-direct-enable] dry-run--apply 后会执行 direct live smoke--apply 会强制要求 direct live 参数。"
return
fi
direct_live_args=(
"${DIRECT_LIVE_SCRIPT}"
--https-base-url "${DIRECT_HTTPS_BASE_URL}"
)
if [[ -n "${DIRECT_HTTP_BASE_URL}" ]]; then
direct_live_args+=(--http-base-url "${DIRECT_HTTP_BASE_URL}")
fi
if [[ -n "${DIRECT_HOST}" ]]; then
direct_live_args+=(--host "${DIRECT_HOST}")
fi
if [[ -n "${DIRECT_REDIRECT_HOST}" ]]; then
direct_live_args+=(--redirect-host "${DIRECT_REDIRECT_HOST}")
fi
if [[ -n "${DIRECT_REDIRECT_BASE_URL}" ]]; then
direct_live_args+=(--redirect-base-url "${DIRECT_REDIRECT_BASE_URL}")
fi
if [[ -n "${DIRECT_PROBE_TOKEN}" ]]; then
direct_live_args+=(--probe-token "${DIRECT_PROBE_TOKEN}")
fi
if [[ -n "${DIRECT_PINGORA_ACCESS_LOG}" ]]; then
direct_live_args+=(--pingora-access-log "${DIRECT_PINGORA_ACCESS_LOG}")
fi
if [[ -n "${DIRECT_ACCESS_LOG_SINCE_LINES}" ]]; then
direct_live_args+=(--access-log-since-lines "${DIRECT_ACCESS_LOG_SINCE_LINES}")
fi
if [[ -n "${DIRECT_SPACETIME_DATABASE}" ]]; then
direct_live_args+=(--spacetime-database "${DIRECT_SPACETIME_DATABASE}")
fi
direct_live_args+=(--require-wss-upgrade)
if [[ -n "${DIRECT_TIMEOUT_MS}" ]]; then
direct_live_args+=(--timeout-ms "${DIRECT_TIMEOUT_MS}")
fi
direct_live_args+=(--json)
print_redacted_command node "${direct_live_args[@]}"
if [[ "${APPLY}" != "true" ]]; then
echo "[pingora-direct-enable] dry-run--apply 后会验证 Pingora HTTPS / HTTP redirect / WSS 直连入口,并解析 direct-access-log 结构化证据。"
return
fi
set +e
direct_live_output="$(node "${direct_live_args[@]}")"
direct_live_status=$?
set -e
printf '%s\n' "${direct_live_output}"
if [[ "${direct_live_status}" -ne 0 ]]; then
exit "${direct_live_status}"
fi
DIRECT_LIVE_OUTPUT="${direct_live_output}" node -- <<'NODE'
const stdout = process.env.DIRECT_LIVE_OUTPUT || '';
const jsonText = extractFirstJsonObject(stdout);
if (!jsonText) {
console.error(
'[pingora-direct-enable] direct live stdout 中未找到 JSON 结果对象,无法确认 direct-access-log 结构化证据。',
);
process.exit(1);
}
let parsed;
try {
parsed = JSON.parse(jsonText);
} catch (error) {
console.error(
`[pingora-direct-enable] direct live JSON 解析失败: ${error.message}`,
);
process.exit(1);
}
const accessLogCheck = parsed?.results?.find(
(item) => item?.name === 'direct-access-log',
);
if (!accessLogCheck) {
console.error(
'[pingora-direct-enable] direct live JSON 缺少 direct-access-log 结构化结果。',
);
process.exit(1);
}
const checked = toNonNegativeInteger(accessLogCheck.checked);
const matchedCount = toNonNegativeInteger(accessLogCheck.matchedCount);
const missingCount = toNonNegativeInteger(accessLogCheck.missingCount);
const mismatchCount = toNonNegativeInteger(accessLogCheck.mismatchCount);
const missingDetailsCount = Array.isArray(accessLogCheck.missing)
? accessLogCheck.missing.length
: null;
const mismatchDetailsCount = Array.isArray(accessLogCheck.mismatches)
? accessLogCheck.mismatches.length
: null;
if (
checked === null ||
matchedCount === null ||
missingCount !== 0 ||
mismatchCount !== 0 ||
matchedCount !== checked ||
missingDetailsCount !== 0 ||
mismatchDetailsCount !== 0
) {
console.error(
`[pingora-direct-enable] direct-access-log 结构化证据未通过: checked=${checked ?? '-'} matched=${matchedCount ?? '-'} missing=${missingCount ?? '-'} mismatches=${mismatchCount ?? '-'}`,
);
process.exit(1);
}
console.log(
`[pingora-direct-enable] direct-access-log evidence checked=${checked} matched=${matchedCount} missing=0 mismatches=0`,
);
function extractFirstJsonObject(text) {
const startIndex = text.indexOf('{');
if (startIndex < 0) {
return '';
}
let depth = 0;
let inString = false;
let escaped = false;
for (let index = startIndex; index < text.length; index += 1) {
const char = text[index];
if (inString) {
if (escaped) {
escaped = false;
} else if (char === '\\') {
escaped = true;
} else if (char === '"') {
inString = false;
}
continue;
}
if (char === '"') {
inString = true;
continue;
}
if (char === '{') {
depth += 1;
continue;
}
if (char === '}') {
depth -= 1;
if (depth === 0) {
return text.slice(startIndex, index + 1);
}
}
}
return '';
}
function toNonNegativeInteger(value) {
const number = Number(value);
if (!Number.isInteger(number) || number < 0) {
return null;
}
return number;
}
NODE
}
echo "[pingora-direct-enable] apply=${APPLY} service=${SERVICE_NAME} template=${TEMPLATE_PATH} dropin=${DROPIN_PATH}"
if [[ "${APPLY}" != "true" ]]; then
echo "[pingora-direct-enable] 当前是 dry-run;确认 preflight 通过后追加 --apply。"
if [[ ! -f "${TEMPLATE_PATH}" ]]; then
echo "[pingora-direct-enable] dry-run 提示:当前模板不存在,--apply 会失败: ${TEMPLATE_PATH}" >&2
fi
fi
run_current_release_audit
run_preflight
run_cmd install -d -m 0755 "$(dirname "${DROPIN_PATH}")"
run_cmd install -m 0644 "${TEMPLATE_PATH}" "${DROPIN_PATH}"
run_systemctl daemon-reload
run_systemctl restart "${SERVICE_NAME}"
verify_systemd_dropin
verify_systemd_exec_start
verify_service_active_after_restart
run_direct_live_smoke_after_enable
if [[ "${STATUS_AFTER}" == "true" ]]; then
run_systemctl status "${SERVICE_NAME}" --no-pager
fi
cat <<EOF
[pingora-direct-enable] 完成。后续请确认:
- systemctl cat ${SERVICE_NAME} 包含 AmbientCapabilities=CAP_NET_BIND_SERVICE
- systemctl show ${SERVICE_NAME} --property=ExecStart --value --no-pager 指向 current release 的 pingora-gateway
- systemctl is-active ${SERVICE_NAME} 为 active
- node ${CURRENT_RELEASE_AUDIT_SCRIPT} --release-root ${CURRENT_RELEASE_ROOT} --require-pingora-gateway --systemd-show --systemd-service ${SERVICE_NAME} 已通过
- npm run check:pingora-direct-preflight -- --env-file /etc/genarrative/pingora-gateway.env --require-live-env --systemd-cat --check-cert-readable --check-service-env-file --check-service-user-cert-readable --check-service-binary-executable --check-ports-free 已通过
- node ${DIRECT_LIVE_SCRIPT} --https-base-url ${DIRECT_HTTPS_BASE_URL:-<direct-https-base-url>} --http-base-url ${DIRECT_HTTP_BASE_URL:-<direct-http-base-url>} --host ${DIRECT_HOST:-<direct-host>} --redirect-host ${DIRECT_REDIRECT_HOST:-<direct-redirect-host>} ${DIRECT_REDIRECT_BASE_URL:+--redirect-base-url ${DIRECT_REDIRECT_BASE_URL} }--pingora-access-log ${DIRECT_PINGORA_ACCESS_LOG:-<pingora-access-log>} --spacetime-database ${DIRECT_SPACETIME_DATABASE:-<database>} --require-wss-upgrade --json 已通过,且 direct-access-log 结构化证据 matched=checked、missing=0、mismatches=0
EOF