From 9fa8a42fc2099fbcb41340b04230e20e889df8d7 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 2 Sep 2026 18:00:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20Phase=205=20=E2=80=94=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=20Diff=20=E9=AA=8C=E8=AF=81=EF=BC=8C?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E9=85=8D=E7=BD=AE=E6=BC=82=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 scripts/config_diff_check.sh:对比渲染 .env 与服务器当前 .env - 输出 ADDED/REMOVED/CHANGED 三类变更 - 敏感值(KEY/SECRET/PASSWORD/TOKEN/URL)脱敏显示(前4字符+***) - 支持首次部署(服务器 .env 不存在时全部标记为 ADDED) - 始终返回退出码 0(仅告警,不阻塞部署) - 末尾输出汇总统计 - 修改 ci-pipeline.yml deploy-staging:SSH 验证后、上传前插入 diff 步骤 - SCP 下载服务器当前 .env 到 CI runner - 运行 diff 脚本后清理临时文件 - .gitignore 添加 .env.current --- .gitea/workflows/ci-pipeline.yml | 9 ++ .gitignore | 1 + scripts/config_diff_check.sh | 177 +++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 scripts/config_diff_check.sh diff --git a/.gitea/workflows/ci-pipeline.yml b/.gitea/workflows/ci-pipeline.yml index e34885632..af2e524ed 100755 --- a/.gitea/workflows/ci-pipeline.yml +++ b/.gitea/workflows/ci-pipeline.yml @@ -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 diff --git a/.gitignore b/.gitignore index 72a62eb23..96b64da22 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ frontend-v21-ui-prototype-final.html !.vscode/settings.json .vscode/extensions.json .coverage +.env.current diff --git a/scripts/config_diff_check.sh b/scripts/config_diff_check.sh new file mode 100644 index 000000000..8b25ebf37 --- /dev/null +++ b/scripts/config_diff_check.sh @@ -0,0 +1,177 @@ +#!/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