296 lines
10 KiB
Bash
296 lines
10 KiB
Bash
#!/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) ns=${NS} kubeconfig=${KUBECONFIG}"
|
||
if ! kubectl -n "$NS" get secret prod-credentials >/dev/null 2>&1; then
|
||
echo "FAIL: 找不到 secret/${NS}/prod-credentials"
|
||
echo " 当前: KUBECONFIG=${KUBECONFIG}"
|
||
echo " test 集群请先: kubectl --kubeconfig=/etc/rancher/k3s/k3s-test.yaml apply -f /data/k3s-test/manifests/secrets.yaml"
|
||
echo " 或: kubectl -n ${NS} get secret"
|
||
exit 1
|
||
fi
|
||
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
|
||
load_secret JWT_SECRET_KEY prod-credentials JWT_SECRET_KEY
|
||
load_secret COS_SECRET_ID prod-credentials COS_SECRET_ID
|
||
load_secret COS_SECRET_KEY prod-credentials COS_SECRET_KEY
|
||
load_secret FEISHU_WEBHOOK prod-credentials FEISHU_WEBHOOK
|
||
load_secret WECHAT_MP_APP_ID prod-credentials WECHAT_MP_APP_ID
|
||
load_secret WECHAT_MP_SECRET prod-credentials WECHAT_MP_SECRET
|
||
load_secret WECHAT_MP_TOKEN prod-credentials WECHAT_MP_TOKEN
|
||
load_secret OMS_TRACK_APP_KEY prod-credentials OMS_TRACK_APP_KEY
|
||
load_secret OMS_TRACK_APP_SECRET prod-credentials OMS_TRACK_APP_SECRET
|
||
load_secret LAZADA_APP_KEY prod-credentials LAZADA_APP_KEY
|
||
load_secret LAZADA_APP_SECRET prod-credentials LAZADA_APP_SECRET
|
||
load_secret SHOPEE_PARTNER_ID prod-credentials SHOPEE_PARTNER_ID
|
||
load_secret SHOPEE_PARTNER_KEY prod-credentials SHOPEE_PARTNER_KEY
|
||
load_secret TIKTOK_APP_KEY prod-credentials TIKTOK_APP_KEY
|
||
load_secret TIKTOK_APP_SECRET prod-credentials TIKTOK_APP_SECRET
|
||
load_secret TEMU_APP_KEY prod-credentials TEMU_APP_KEY
|
||
load_secret TEMU_APP_SECRET prod-credentials TEMU_APP_SECRET
|
||
|
||
: "${MYSQL_ROOT_PASSWORD:?缺少 MYSQL_ROOT_PASSWORD(env 或 secret/${NS}/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)}"
|
||
: "${COS_SECRET_ID:?缺少 COS_SECRET_ID}"
|
||
: "${COS_SECRET_KEY:?缺少 COS_SECRET_KEY}"
|
||
: "${FEISHU_WEBHOOK:?缺少 FEISHU_WEBHOOK}"
|
||
: "${WECHAT_MP_APP_ID:?缺少 WECHAT_MP_APP_ID}"
|
||
: "${WECHAT_MP_SECRET:?缺少 WECHAT_MP_SECRET}"
|
||
: "${WECHAT_MP_TOKEN:?缺少 WECHAT_MP_TOKEN}"
|
||
: "${OMS_TRACK_APP_KEY:?缺少 OMS_TRACK_APP_KEY}"
|
||
: "${OMS_TRACK_APP_SECRET:?缺少 OMS_TRACK_APP_SECRET}"
|
||
: "${LAZADA_APP_KEY:?缺少 LAZADA_APP_KEY}"
|
||
: "${LAZADA_APP_SECRET:?缺少 LAZADA_APP_SECRET}"
|
||
: "${SHOPEE_PARTNER_ID:?缺少 SHOPEE_PARTNER_ID}"
|
||
: "${SHOPEE_PARTNER_KEY:?缺少 SHOPEE_PARTNER_KEY}"
|
||
: "${TIKTOK_APP_KEY:?缺少 TIKTOK_APP_KEY}"
|
||
: "${TIKTOK_APP_SECRET:?缺少 TIKTOK_APP_SECRET}"
|
||
: "${TEMU_APP_KEY:?缺少 TEMU_APP_KEY}"
|
||
: "${TEMU_APP_SECRET:?缺少 TEMU_APP_SECRET}"
|
||
|
||
# Consul:prod 用 ClusterIP(runner 在簇内);test 用内网 NodePort(见 consul-ci / TEST_CONSUL_HTTP_ADDR)
|
||
if [[ -z "$CONSUL_HTTP_ADDR" ]]; then
|
||
if [[ "$NS" == "test" ]]; then
|
||
# 安全组:仅放行 CI 机内网 → 测试机 30850;勿对公网开放
|
||
CONSUL_HTTP_ADDR="${TEST_CONSUL_HTTP_ADDR:-http://172.16.16.13:30850}"
|
||
else
|
||
CIP="$(kubectl -n "$NS" get svc consul -o jsonpath='{.spec.clusterIP}')"
|
||
CONSUL_HTTP_ADDR="http://${CIP}:8500"
|
||
fi
|
||
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 \
|
||
COS_SECRET_ID COS_SECRET_KEY FEISHU_WEBHOOK \
|
||
WECHAT_MP_APP_ID WECHAT_MP_SECRET WECHAT_MP_TOKEN \
|
||
OMS_TRACK_APP_KEY OMS_TRACK_APP_SECRET \
|
||
LAZADA_APP_KEY LAZADA_APP_SECRET \
|
||
SHOPEE_PARTNER_ID SHOPEE_PARTNER_KEY \
|
||
TIKTOK_APP_KEY TIKTOK_APP_SECRET \
|
||
TEMU_APP_KEY TEMU_APP_SECRET
|
||
envsubst '${MYSQL_ROOT_PASSWORD} ${REDIS_PASSWORD} ${RABBITMQ_DEFAULT_PASS} ${ELASTIC_PASSWORD} ${JWT_SECRET_KEY} ${COS_SECRET_ID} ${COS_SECRET_KEY} ${FEISHU_WEBHOOK} ${WECHAT_MP_APP_ID} ${WECHAT_MP_SECRET} ${WECHAT_MP_TOKEN} ${OMS_TRACK_APP_KEY} ${OMS_TRACK_APP_SECRET} ${LAZADA_APP_KEY} ${LAZADA_APP_SECRET} ${SHOPEE_PARTNER_ID} ${SHOPEE_PARTNER_KEY} ${TIKTOK_APP_KEY} ${TIKTOK_APP_SECRET} ${TEMU_APP_KEY} ${TEMU_APP_SECRET}' \
|
||
<"$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
|
||
|
||
# 暂时关闭:改配置后自动 rollout restart API(需要时把下面取消注释,或 CI 设 RESTART_APIS=1)
|
||
# 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
|
||
if [[ "$RESTART_APIS" == "1" ]]; then
|
||
echo "==> SKIP rollout restart(已暂时关闭;RESTART_APIS=1 被忽略)"
|
||
fi
|
||
|
||
echo "DONE"
|