#!/usr/bin/env bash # =========================================================== # config_diff_check.sh — 对比渲染 .env 与服务器当前 .env # =========================================================== # 用法: scripts/config_diff_check.sh # # 输出: # + 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 " >&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