config/scripts/sync-to-consul.sh
xiaolipro a4f7fa750a
All checks were successful
Sync Consul Config / sync (push) Successful in 2s
feat:支持jsonc
2026-09-08 14:38:28 +08:00

242 lines
7.0 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
# 将仓库 idwms/*.json 渲染密钥后写入 Consul KV
# 用法:
# ./scripts/sync-to-consul.sh # 同步全部
# ./scripts/sync-to-consul.sh admin # 只同步 idwms/admin
# RESTART_APIS=1 ./scripts/sync-to-consul.sh
#
# 密钥来源(优先):
# 1) 环境变量 MYSQL_ROOT_PASSWORD / REDIS_PASSWORD / ...
# 2) K8s Secret prod-credentials(及可选 jwt-secret)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
export KUBECONFIG="${KUBECONFIG:-/etc/rancher/k3s/k3s.yaml}"
NS="${NAMESPACE:-prod}"
CONSUL_HTTP_ADDR="${CONSUL_HTTP_ADDR:-}"
RESTART_APIS="${RESTART_APIS:-0}"
IDWMS_DIR="${ROOT}/idwms"
need() { command -v "$1" >/dev/null || { echo "FAIL: 需要 $1"; exit 1; }; }
need kubectl
need curl
need python3
command -v envsubst >/dev/null || { echo "FAIL: 需要 envsubst(apt install gettext-base)"; exit 1; }
# 按 JSONC 校验(允许 //、/* */、尾逗号);通过后原样写入 Consul,不剥离注释
jsonc_check() {
python3 - "$1" <<'PY'
import json, sys
def strip_jsonc(text: str) -> str:
out = []
i, n = 0, len(text)
in_str = escape = False
while i < n:
c = text[i]
if in_str:
out.append(c)
if escape:
escape = False
elif c == "\\":
escape = True
elif c == '"':
in_str = False
i += 1
continue
if c == '"':
in_str = True
out.append(c)
i += 1
continue
if c == "/" and i + 1 < n:
nxt = text[i + 1]
if nxt == "/":
i += 2
while i < n and text[i] not in "\r\n":
i += 1
continue
if nxt == "*":
i += 2
while i + 1 < n and not (text[i] == "*" and text[i + 1] == "/"):
i += 1
i = min(i + 2, n)
continue
out.append(c)
i += 1
cleaned = []
s = "".join(out)
i, n = 0, len(s)
in_str = escape = False
while i < n:
c = s[i]
if in_str:
cleaned.append(c)
if escape:
escape = False
elif c == "\\":
escape = True
elif c == '"':
in_str = False
i += 1
continue
if c == '"':
in_str = True
cleaned.append(c)
i += 1
continue
if c == ",":
j = i + 1
while j < n and s[j] in " \t\r\n":
j += 1
if j < n and s[j] in "]}":
i += 1
continue
cleaned.append(c)
i += 1
return "".join(cleaned)
path = sys.argv[1]
raw = open(path, encoding="utf-8").read()
try:
json.loads(strip_jsonc(raw))
except json.JSONDecodeError as e:
print(f"FAIL: {path} 不是合法 JSONC: {e}", file=sys.stderr)
sys.exit(1)
PY
}
secret_b64() {
local name="$1" key="$2"
kubectl -n "$NS" get secret "$name" -o "jsonpath={.data.${key}}" 2>/dev/null || true
}
load_secret() {
local var="$1" secret="$2" key="$3"
if [[ -n "${!var:-}" ]]; then
return 0
fi
local raw
raw="$(secret_b64 "$secret" "$key")"
if [[ -n "$raw" ]]; then
export "$var"="$(printf '%s' "$raw" | base64 -d)"
fi
}
echo "==> 加载密钥(K8s Secret / env)"
load_secret MYSQL_ROOT_PASSWORD prod-credentials MYSQL_ROOT_PASSWORD
load_secret REDIS_PASSWORD prod-credentials REDIS_PASSWORD
load_secret RABBITMQ_DEFAULT_PASS prod-credentials RABBITMQ_DEFAULT_PASS
load_secret ELASTIC_PASSWORD prod-credentials ELASTIC_PASSWORD
load_secret JWT_SECRET_KEY jwt-secret JWT_SECRET_KEY
# 兼容:JWT 也放在 prod-credentials
load_secret JWT_SECRET_KEY prod-credentials JWT_SECRET_KEY
: "${MYSQL_ROOT_PASSWORD:?缺少 MYSQL_ROOT_PASSWORD(env 或 secret/prod-credentials)}"
: "${REDIS_PASSWORD:?缺少 REDIS_PASSWORD}"
: "${RABBITMQ_DEFAULT_PASS:?缺少 RABBITMQ_DEFAULT_PASS}"
: "${ELASTIC_PASSWORD:?缺少 ELASTIC_PASSWORD}"
: "${JWT_SECRET_KEY:?缺少 JWT_SECRET_KEY(请创建 secret/jwt-secret 或写入 prod-credentials)}"
if [[ -z "$CONSUL_HTTP_ADDR" ]]; then
CIP="$(kubectl -n "$NS" get svc consul -o jsonpath='{.spec.clusterIP}')"
CONSUL_HTTP_ADDR="http://${CIP}:8500"
fi
echo "==> Consul ${CONSUL_HTTP_ADDR}"
# ACL(可选)
if [[ -z "${CONSUL_HTTP_TOKEN:-}" ]]; then
raw="$(secret_b64 consul-acl CONSUL_HTTP_TOKEN)"
if [[ -n "$raw" ]]; then
export CONSUL_HTTP_TOKEN="$(printf '%s' "$raw" | base64 -d)"
fi
fi
CURL_AUTH=()
if [[ -n "${CONSUL_HTTP_TOKEN:-}" ]]; then
CURL_AUTH=(-H "X-Consul-Token: ${CONSUL_HTTP_TOKEN}")
fi
TARGETS=()
if [[ $# -eq 0 ]]; then
for f in "${IDWMS_DIR}"/*.json; do
[[ -f "$f" ]] || continue
TARGETS+=("$(basename "$f" .json)")
done
else
TARGETS=("$@")
fi
[[ ${#TARGETS[@]} -gt 0 ]] || { echo "FAIL: 无同步目标"; exit 1; }
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
put_kv() {
local key="$1" file="$2"
local code
code="$(curl -sS -o /tmp/consul-put.out -w '%{http_code}' \
"${CURL_AUTH[@]}" \
-X PUT --data-binary @"$file" \
"${CONSUL_HTTP_ADDR}/v1/kv/${key}")"
if [[ "$code" != "200" ]]; then
echo "FAIL: PUT ${key} HTTP ${code}"
cat /tmp/consul-put.out 2>/dev/null || true
exit 1
fi
echo "OK idwms/${key##*/} → ${key}"
}
RESTART_SET=()
for name in "${TARGETS[@]}"; do
src="${IDWMS_DIR}/${name}.json"
[[ -f "$src" ]] || { echo "FAIL: 无 ${src}"; exit 1; }
# 仅替换已知占位符,避免 JSON 里其它 $ 被误伤
export MYSQL_ROOT_PASSWORD REDIS_PASSWORD RABBITMQ_DEFAULT_PASS ELASTIC_PASSWORD JWT_SECRET_KEY
envsubst '${MYSQL_ROOT_PASSWORD} ${REDIS_PASSWORD} ${RABBITMQ_DEFAULT_PASS} ${ELASTIC_PASSWORD} ${JWT_SECRET_KEY}' \
<"$src" >"${TMP}/${name}.json"
# 禁止未替换的占位符漏网(注释里的 ${...} 也会拦)
if grep -qE '\$\{[A-Z0-9_]+\}' "${TMP}/${name}.json"; then
echo "FAIL: ${name}.json 仍有未替换占位符:"
grep -E '\$\{[A-Z0-9_]+\}' "${TMP}/${name}.json" || true
exit 1
fi
# JSONC 校验通过后,原样 PUT(保留注释,便于和 git 对照排查)
if ! jsonc_check "${TMP}/${name}.json"; then
exit 1
fi
put_kv "idwms/${name}" "${TMP}/${name}.json"
case "$name" in
admin) RESTART_SET+=(admin-api) ;;
itfxapi) RESTART_SET+=(interfaceforward-api) ;;
trackapi) RESTART_SET+=(track-api) ;;
common)
RESTART_SET+=(admin-api interfaceforward-api track-api)
;;
esac
done
if [[ "$RESTART_APIS" == "1" ]]; then
# 去重
readarray -t RESTART_SET < <(printf '%s\n' "${RESTART_SET[@]}" | sort -u)
echo "==> rollout restart: ${RESTART_SET[*]}"
for d in "${RESTART_SET[@]}"; do
[[ -n "$d" ]] || continue
if kubectl -n "$NS" get "deploy/${d}" >/dev/null 2>&1; then
kubectl -n "$NS" rollout restart "deploy/${d}"
kubectl -n "$NS" rollout status "deploy/${d}" --timeout=180s || true
else
echo "SKIP deploy/${d}(不存在)"
fi
done
fi
echo "DONE"