feat(config): Phase 5 — 配置 Diff 验证,检测配置漂移 #1632

Merged
auto-approve-bot merged 1 commits from feat/config-phase5-diff into develop 2026-09-02 18:15:35 +08:00
3 changed files with 187 additions and 0 deletions
+9
View File
@@ -1266,6 +1266,15 @@ jobs:
ssh -p "$staging_port" -i "$key_path" -o StrictHostKeyChecking=no "${staging_user}@${staging_host}" "echo SSH_CONNECTION_OK && hostname"
echo "SSH connection verified"
# 配置 Diff 检查:下载服务器当前 .env,对比渲染结果,检测漂移
echo "Running config diff check..."
scp -P "$staging_port" -i "$key_path" -o StrictHostKeyChecking=no \
"${staging_user}@${staging_host}:/var/lib/xiaoxia-saas-staging/.env" .env.current 2>/dev/null \
|| touch .env.current # 首次部署时文件不存在,创建空文件
bash scripts/config_diff_check.sh .env.rendered .env.current
rm -f .env.current
echo "Config diff check done"
# 上传渲染后的 .env 到服务器(替代服务器上旧的 .env)
echo "Uploading rendered .env to staging server..."
# 备份旧 .env
+1
View File
@@ -59,3 +59,4 @@ frontend-v21-ui-prototype-final.html
!.vscode/settings.json
.vscode/extensions.json
.coverage
.env.current
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env bash
# ===========================================================
# config_diff_check.sh — 对比渲染 .env 与服务器当前 .env
# ===========================================================
# 用法: scripts/config_diff_check.sh <rendered_file> <current_file>
#
# 输出:
# + ADDED 渲染文件有、当前文件没有(新增配置)
# - REMOVED 当前文件有、渲染文件没有(将被删除)
# ~ CHANGED 两边都有但值不同(将被覆盖)
#
# 敏感值脱敏:KEY/SECRET/PASSWORD/TOKEN/URL 类变量只显示前4字符+***
# 退出码: 始终返回 0(仅告警,不阻塞部署)
# ===========================================================
set -u
RENDERED_FILE="${1:-}"
CURRENT_FILE="${2:-}"
if [ -z "$RENDERED_FILE" ] || [ -z "$CURRENT_FILE" ]; then
echo "ERROR: 用法: $0 <rendered_file> <current_file>" >&2
exit 0
fi
if [ ! -f "$RENDERED_FILE" ]; then
echo "ERROR: 渲染文件不存在: $RENDERED_FILE" >&2
exit 0
fi
# 判断是否为敏感变量(键名包含以下关键词)
is_sensitive() {
local key="$1"
case "$key" in
*KEY*|*SECRET*|*PASSWORD*|*TOKEN*|*URL*|*BROKER*|*BACKEND*) return 0 ;;
*) return 1 ;;
esac
}
# 脱敏:敏感值只显示前4字符+***
mask_value() {
local key="$1"
local value="$2"
if is_sensitive "$key"; then
if [ ${#value} -le 4 ]; then
echo "****"
else
echo "${value:0:4}***"
fi
else
echo "$value"
fi
}
# 解析文件为 KEY=VALUE(忽略注释和空行)
parse_env() {
local file="$1"
grep -vE '^\s*#|^\s*$' "$file" 2>/dev/null | while IFS= read -r line; do
# 只取第一个 = 之前的部分作为 key
key="${line%%=*}"
value="${line#*=}"
# 跳过无效行
if [ -n "$key" ] && [ "$key" != "$line" ]; then
echo "${key}=${value}"
fi
done
}
echo "=========================================="
echo " 配置 Diff 检查(检测配置漂移)"
echo "=========================================="
echo "渲染文件: $RENDERED_FILE"
echo "当前文件: $CURRENT_FILE"
echo ""
# 解析两个文件
if [ ! -f "$CURRENT_FILE" ] || [ ! -s "$CURRENT_FILE" ]; then
# 服务器 .env 不存在或为空(首次部署)
echo "⚠️ 服务器 .env 不存在或为空(可能是首次部署)"
echo " 所有配置项将标记为 ADDED"
echo ""
added=0
while IFS='=' read -r key value; do
[ -z "$key" ] && continue
masked=$(mask_value "$key" "$value")
echo " + ADDED ${key}=${masked}"
added=$((added + 1))
done < <(parse_env "$RENDERED_FILE")
echo ""
echo "=========================================="
echo " 汇总: 新增 ${added} 项 | 删除 0 项 | 变更 0 项 | 无变化 0 项"
echo "=========================================="
exit 0
fi
# 用临时文件存储解析结果
tmp_rendered=$(mktemp)
tmp_current=$(mktemp)
trap "rm -f $tmp_rendered $tmp_current" EXIT
parse_env "$RENDERED_FILE" | sort > "$tmp_rendered"
parse_env "$CURRENT_FILE" | sort > "$tmp_current"
added=0
removed=0
changed=0
unchanged=0
echo "--- 新增配置(渲染文件有、当前文件无)---"
# 找 ADDED:渲染文件有但当前文件没有的 key
while IFS='=' read -r key value; do
[ -z "$key" ] && continue
current_line=$(grep -m1 "^${key}=" "$tmp_current" 2>/dev/null || true)
if [ -z "$current_line" ]; then
masked=$(mask_value "$key" "$value")
echo " + ADDED ${key}=${masked}"
added=$((added + 1))
fi
done < "$tmp_rendered"
if [ "$added" -eq 0 ]; then
echo " (无)"
fi
echo ""
echo "--- 删除配置(当前文件有、渲染文件无)---"
# 找 REMOVED:当前文件有但渲染文件没有的 key
while IFS='=' read -r key value; do
[ -z "$key" ] && continue
rendered_line=$(grep -m1 "^${key}=" "$tmp_rendered" 2>/dev/null || true)
if [ -z "$rendered_line" ]; then
masked=$(mask_value "$key" "$value")
echo " - REMOVED ${key}=${masked}"
removed=$((removed + 1))
fi
done < "$tmp_current"
if [ "$removed" -eq 0 ]; then
echo " (无)"
fi
echo ""
echo "--- 变更配置(两边都有但值不同)---"
# 找 CHANGED:两边都有但值不同
while IFS='=' read -r key value; do
[ -z "$key" ] && continue
current_line=$(grep -m1 "^${key}=" "$tmp_current" 2>/dev/null || true)
if [ -n "$current_line" ]; then
current_value="${current_line#*=}"
if [ "$value" != "$current_value" ]; then
masked_new=$(mask_value "$key" "$value")
masked_old=$(mask_value "$key" "$current_value")
echo " ~ CHANGED ${key}: ${masked_old}${masked_new}"
changed=$((changed + 1))
else
unchanged=$((unchanged + 1))
fi
fi
done < "$tmp_rendered"
if [ "$changed" -eq 0 ]; then
echo " (无)"
fi
echo ""
echo "=========================================="
echo " 汇总: 新增 ${added} 项 | 删除 ${removed} 项 | 变更 ${changed} 项 | 无变化 ${unchanged}"
echo "=========================================="
if [ "$added" -gt 0 ] || [ "$removed" -gt 0 ] || [ "$changed" -gt 0 ]; then
echo "⚠️ 检测到配置漂移,请确认以上变更是否符合预期"
else
echo "✅ 配置无漂移,与服务器当前配置一致"
fi
exit 0