chore: sync changes
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Gitea 备份脚本 (套件版 / Docker 版 通用)
|
||||
# =============================================================================
|
||||
# 用途: 定期备份 Gitea 数据,防再次出现"卸载清空"惨案
|
||||
#
|
||||
# 备份内容:
|
||||
# - Gitea 配置文件 (app.ini)
|
||||
# - SQLite 数据库
|
||||
# - 所有仓库 (git bare repos)
|
||||
# - LFS 数据
|
||||
# - 附件 / avatars
|
||||
#
|
||||
# 用法:
|
||||
# sudo bash scripts/backup-gitea.sh # 默认备份到 /volume1/backups/gitea/
|
||||
# sudo bash scripts/backup-gitea.sh /path/to/backup # 自定义备份目录
|
||||
# sudo bash scripts/backup-gitea.sh --keep 30 # 保留 30 天
|
||||
# sudo bash scripts/backup-gitea.sh --restore latest # 恢复最新备份(谨慎)
|
||||
#
|
||||
# Cron 建议 (每天凌晨 3 点):
|
||||
# 0 3 * * * /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh >> /var/log/gitea-backup.log 2>&1
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
|
||||
# 默认参数
|
||||
BACKUP_DIR="${1:-/volume1/backups/gitea}"
|
||||
KEEP_DAYS=7
|
||||
RESTORE=""
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--keep) KEEP_DAYS="$2"; shift 2 ;;
|
||||
--restore) RESTORE="$2"; shift 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
BACKUP_NAME="gitea-backup-${TIMESTAMP}"
|
||||
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
|
||||
|
||||
# =============================================================================
|
||||
# 路径探测
|
||||
# =============================================================================
|
||||
detect_gitea_paths() {
|
||||
info "探测 Gitea 数据目录..."
|
||||
|
||||
# 套件版默认路径
|
||||
if [ -d "/volume1/@appdata/gitea" ]; then
|
||||
GITEA_HOME="/volume1/@appdata/gitea"
|
||||
info " 套件版: $GITEA_HOME"
|
||||
# Docker 版常见路径
|
||||
elif [ -d "/volume1/docker/gitea" ]; then
|
||||
GITEA_HOME="/volume1/docker/gitea"
|
||||
info " Docker 版: $GITEA_HOME"
|
||||
else
|
||||
error "未找到 Gitea 数据目录,请手动指定 GITEA_HOME 环境变量"
|
||||
fi
|
||||
|
||||
# 找配置和数据子目录
|
||||
for sub in gitea data config repos lfs avatars; do
|
||||
if [ -d "$GITEA_HOME/$sub" ]; then
|
||||
declare -g "GITEA_${sub^^}=$GITEA_HOME/$sub"
|
||||
fi
|
||||
done
|
||||
|
||||
# 找 SQLite 数据库
|
||||
for db in "$GITEA_HOME/gitea/data/gitea.db" "$GITEA_HOME/data/gitea.db"; do
|
||||
[ -f "$db" ] && GITEA_DB="$db" && break
|
||||
done
|
||||
|
||||
if [ -z "$GITEA_DB" ]; then
|
||||
warn "未找到 SQLite 数据库,可能用 MySQL/Postgres(此脚本不备份 DB)"
|
||||
else
|
||||
info " 数据库: $GITEA_DB"
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# 备份模式
|
||||
# =============================================================================
|
||||
do_backup() {
|
||||
info "=== Gitea 备份开始 ==="
|
||||
info "时间: $TIMESTAMP"
|
||||
info "目标: $BACKUP_PATH"
|
||||
|
||||
mkdir -p "$BACKUP_PATH"
|
||||
detect_gitea_paths
|
||||
|
||||
# 1. 备份 app.ini 配置
|
||||
if [ -f "$GITEA_HOME/gitea/conf/app.ini" ]; then
|
||||
mkdir -p "$BACKUP_PATH/conf"
|
||||
cp "$GITEA_HOME/gitea/conf/app.ini" "$BACKUP_PATH/conf/"
|
||||
ok "备份配置 app.ini"
|
||||
fi
|
||||
|
||||
# 2. 备份 SQLite(必须先停 Gitea,或用 .backup 命令)
|
||||
if [ -n "$GITEA_DB" ] && [ -f "$GITEA_DB" ]; then
|
||||
# 用 sqlite3 .backup(支持热备)
|
||||
if command -v sqlite3 &> /dev/null; then
|
||||
sqlite3 "$GITEA_DB" ".backup '$BACKUP_PATH/gitea.db'"
|
||||
ok "SQLite 热备完成 (sqlite3 .backup)"
|
||||
else
|
||||
warn "无 sqlite3 命令,改用文件复制(可能不一致)"
|
||||
cp "$GITEA_DB" "$BACKUP_PATH/gitea.db"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. 备份仓库 (bare git)
|
||||
if [ -d "$GITEA_HOME/gitea/repos" ]; then
|
||||
info "备份仓库目录(可能大)..."
|
||||
tar -czf "$BACKUP_PATH/repos.tar.gz" \
|
||||
-C "$GITEA_HOME/gitea" repos 2>/dev/null || \
|
||||
warn "仓库备份失败(可能权限不足,需 sudo)"
|
||||
ok "仓库 tar 完成: $(du -h "$BACKUP_PATH/repos.tar.gz" 2>/dev/null | cut -f1)"
|
||||
fi
|
||||
|
||||
# 4. 备份 LFS
|
||||
if [ -d "$GITEA_HOME/gitea/lfs" ]; then
|
||||
tar -czf "$BACKUP_PATH/lfs.tar.gz" \
|
||||
-C "$GITEA_HOME/gitea" lfs 2>/dev/null || warn "LFS 备份失败"
|
||||
fi
|
||||
|
||||
# 5. 备份附件 / avatars
|
||||
if [ -d "$GITEA_HOME/gitea/avatars" ]; then
|
||||
cp -r "$GITEA_HOME/gitea/avatars" "$BACKUP_PATH/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 6. 备份元信息
|
||||
cat > "$BACKUP_PATH/backup.meta" <<EOF
|
||||
# Gitea 备份元信息
|
||||
timestamp=$TIMESTAMP
|
||||
hostname=$(hostname)
|
||||
gitea_home=$GITEA_HOME
|
||||
db_path=$GITEA_DB
|
||||
backup_size=$(du -sh "$BACKUP_PATH" | cut -f1)
|
||||
git_rev=$(cd "$GITEA_HOME" 2>/dev/null && git rev-parse HEAD 2>/dev/null || echo "N/A")
|
||||
EOF
|
||||
|
||||
# 7. 打包成单文件(方便转存)
|
||||
info "打包最终备份..."
|
||||
tar -czf "${BACKUP_PATH}.tar.gz" -C "$BACKUP_DIR" "$BACKUP_NAME"
|
||||
rm -rf "$BACKUP_PATH"
|
||||
ok "最终备份: ${BACKUP_PATH}.tar.gz"
|
||||
ok " 大小: $(du -h "${BACKUP_PATH}.tar.gz" | cut -f1)"
|
||||
|
||||
# 8. 清理旧备份
|
||||
info "清理 $KEEP_DAYS 天前的旧备份..."
|
||||
local cleaned=0
|
||||
while IFS= read -r old; do
|
||||
rm -f "$old"
|
||||
cleaned=$((cleaned+1))
|
||||
done < <(find "$BACKUP_DIR" -maxdepth 1 -name "gitea-backup-*.tar.gz" -mtime +$KEEP_DAYS)
|
||||
ok "清理旧备份: $cleaned 个"
|
||||
|
||||
info "=== 备份完成 ==="
|
||||
info "建议: 把 ${BACKUP_PATH}.tar.gz scp 到异地(例 NAS2 / 阿里云 OSS / 本地电脑)"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# 恢复模式
|
||||
# =============================================================================
|
||||
do_restore() {
|
||||
if [ -z "$RESTORE" ]; then
|
||||
error "未指定要恢复的备份名(--restore latest|YYYYMMDD-HHMMSS)"
|
||||
fi
|
||||
|
||||
if [ "$RESTORE" = "latest" ]; then
|
||||
RESTORE_FILE=$(ls -t "$BACKUP_DIR"/gitea-backup-*.tar.gz 2>/dev/null | head -1)
|
||||
[ -z "$RESTORE_FILE" ] && error "找不到备份文件"
|
||||
else
|
||||
RESTORE_FILE="$BACKUP_DIR/gitea-backup-${RESTORE}.tar.gz"
|
||||
[ -f "$RESTORE_FILE" ] || error "备份文件不存在: $RESTORE_FILE"
|
||||
fi
|
||||
|
||||
warn "!!! 恢复操作会覆盖当前 Gitea 数据 !!!"
|
||||
warn " 备份文件: $RESTORE_FILE"
|
||||
warn " 按 Ctrl+C 取消,或 5 秒后继续"
|
||||
sleep 5
|
||||
|
||||
info "解压备份..."
|
||||
TMP_DIR=$(mktemp -d)
|
||||
tar -xzf "$RESTORE_FILE" -C "$TMP_DIR"
|
||||
BACKUP_CONTENT=$(ls "$TMP_DIR" | head -1)
|
||||
[ -z "$BACKUP_CONTENT" ] && error "备份文件内容为空"
|
||||
|
||||
detect_gitea_paths
|
||||
EXTRACT_DIR="$TMP_DIR/$BACKUP_CONTENT"
|
||||
|
||||
# 停 Gitea(套件 / Docker)
|
||||
info "停 Gitea..."
|
||||
if command -v synopkg &> /dev/null; then
|
||||
sudo synopkg stop Gitea 2>/dev/null || true
|
||||
fi
|
||||
docker stop gitea 2>/dev/null || true
|
||||
|
||||
# 恢复 app.ini
|
||||
if [ -f "$EXTRACT_DIR/conf/app.ini" ]; then
|
||||
cp "$EXTRACT_DIR/conf/app.ini" "$GITEA_HOME/gitea/conf/app.ini"
|
||||
ok "恢复 app.ini"
|
||||
fi
|
||||
|
||||
# 恢复 DB
|
||||
if [ -f "$EXTRACT_DIR/gitea.db" ]; then
|
||||
cp "$EXTRACT_DIR/gitea.db" "$GITEA_DB"
|
||||
ok "恢复 SQLite"
|
||||
fi
|
||||
|
||||
# 恢复 repos
|
||||
if [ -f "$EXTRACT_DIR/repos.tar.gz" ]; then
|
||||
rm -rf "$GITEA_HOME/gitea/repos"
|
||||
tar -xzf "$EXTRACT_DIR/repos.tar.gz" -C "$GITEA_HOME/gitea/"
|
||||
ok "恢复 repos"
|
||||
fi
|
||||
|
||||
# 启动 Gitea
|
||||
info "启动 Gitea..."
|
||||
if command -v synopkg &> /dev/null; then
|
||||
sudo synopkg start Gitea
|
||||
fi
|
||||
docker start gitea 2>/dev/null || true
|
||||
|
||||
rm -rf "$TMP_DIR"
|
||||
ok "恢复完成"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# 入口
|
||||
# =============================================================================
|
||||
if [ -n "$RESTORE" ]; then
|
||||
do_restore
|
||||
else
|
||||
do_backup
|
||||
fi
|
||||
@@ -0,0 +1,329 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# workbuddy / Claude 推送前 4 件套预检脚本
|
||||
# =============================================================================
|
||||
# 用途: 推送前自检 4 件套,发现 P0 漏洞立即拦截
|
||||
# 1. 鉴权: 新增/修改端点是否带 Depends(get_current_*) 鉴权
|
||||
# 2. 依赖: 新增 import 是否同步 requirements.txt / package.json
|
||||
# 3. alembic: model schema 变化是否生成迁移脚本
|
||||
# 4. 配置: nginx / docker / conf 改动是否完整
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/pre-commit-check.sh # 检查 staged 变更
|
||||
# bash scripts/pre-commit-check.sh --staged # 同上(默认)
|
||||
# bash scripts/pre-commit-check.sh --branch # 检查当前分支相对 main 的全部变更
|
||||
# bash scripts/pre-commit-check.sh --strict # 严格模式(任何 warn 也失败)
|
||||
# bash scripts/pre-commit-check.sh --json # 输出 JSON 格式(给 workbuddy 解析)
|
||||
#
|
||||
# 退出码:
|
||||
# 0 = 全过 / 仅 INFO
|
||||
# 1 = 有 WARN(--strict 下)
|
||||
# 2 = 有 ERROR(必须修)
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
PASS_COUNT=0
|
||||
WARN_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
WARN_LIST=()
|
||||
FAIL_LIST=()
|
||||
|
||||
pass() { PASS_COUNT=$((PASS_COUNT+1)); echo -e "${GREEN}[PASS]${NC} $1"; }
|
||||
warn() { WARN_COUNT=$((WARN_COUNT+1)); WARN_LIST+=("$1"); echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
fail() { FAIL_COUNT=$((FAIL_COUNT+1)); FAIL_LIST+=("$1"); echo -e "${RED}[FAIL]${NC} $1"; }
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
|
||||
# 参数
|
||||
MODE="staged"
|
||||
STRICT=false
|
||||
JSON_OUT=false
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--staged) MODE="staged" ;;
|
||||
--branch) MODE="branch" ;;
|
||||
--strict) STRICT=true ;;
|
||||
--json) JSON_OUT=true ;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# 收集变更文件
|
||||
if [ "$MODE" = "staged" ]; then
|
||||
info "检查 staged 变更..."
|
||||
CHANGED=$(git diff --cached --name-only)
|
||||
BASE="staged"
|
||||
elif [ "$MODE" = "branch" ]; then
|
||||
info "检查当前分支 vs main 的变更..."
|
||||
BASE_BRANCH="main"
|
||||
git rev-parse --verify "$BASE_BRANCH" >/dev/null 2>&1 || BASE_BRANCH="origin/main"
|
||||
git rev-parse --verify "$BASE_BRANCH" >/dev/null 2>&1 || {
|
||||
fail "找不到 main 分支,请先 git fetch"
|
||||
exit 2
|
||||
}
|
||||
CHANGED=$(git diff --name-only "$BASE_BRANCH"...HEAD)
|
||||
fi
|
||||
|
||||
if [ -z "$CHANGED" ]; then
|
||||
info "无变更文件,跳过"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
info "变更文件 ($([ "$MODE" = "staged" ] && echo "staged" || echo "branch")):"
|
||||
echo "$CHANGED" | sed 's/^/ /'
|
||||
|
||||
# =============================================================================
|
||||
# 检查 1: 鉴权
|
||||
# =============================================================================
|
||||
info ""
|
||||
info "── 检查 1/4: 鉴权 (Depends(get_current_*))"
|
||||
|
||||
check_auth() {
|
||||
local file="$1"
|
||||
# 只检查后端 api 路由文件
|
||||
case "$file" in
|
||||
backend/app/api/*.py|backend/app/api/**/*.py) ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
# 跳过非路由文件
|
||||
case "$file" in
|
||||
*schemas*) return 0 ;;
|
||||
*_test*) return 0 ;;
|
||||
esac
|
||||
|
||||
# 看 diff 是否新增/修改了路由(@router.*)
|
||||
local diff
|
||||
if [ "$MODE" = "staged" ]; then
|
||||
diff=$(git diff --cached "$file")
|
||||
else
|
||||
diff=$(git diff "$BASE_BRANCH"...HEAD -- "$file")
|
||||
fi
|
||||
# 有 router 装饰器改动?
|
||||
if ! echo "$diff" | grep -qE '^\+.*@router\.(get|post|put|delete|patch)'; then
|
||||
return 0 # 无路由变化,跳过
|
||||
fi
|
||||
|
||||
# 看新增/修改的函数是否带 Depends
|
||||
local func_diff
|
||||
func_diff=$(echo "$diff" | grep -E '^\+.*(async )?def [a-z_]+\(')
|
||||
if echo "$func_diff" | grep -qE 'Depends\(.*get_current'; then
|
||||
pass " $file: 新路由有 Depends 鉴权"
|
||||
elif echo "$func_diff" | grep -qE '@router\.(get|post|put|delete|patch)'; then
|
||||
# 新增路由但没 Depends → 极可能是 P0 漏洞
|
||||
local new_routes
|
||||
new_routes=$(echo "$func_diff" | grep -B 5 'def [a-z_]' | grep -E '^\+.*@router\.' | head -5)
|
||||
if [ -n "$new_routes" ]; then
|
||||
fail " $file: 新增路由可能无鉴权: $(echo "$new_routes" | wc -l) 处"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
for f in $CHANGED; do
|
||||
[ -f "$f" ] && check_auth "$f"
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# 检查 2: 依赖
|
||||
# =============================================================================
|
||||
info ""
|
||||
info "── 检查 2/4: 依赖 (requirements.txt / package.json)"
|
||||
|
||||
check_deps() {
|
||||
local file="$1"
|
||||
# 只检查 Python / JS 文件
|
||||
case "$file" in
|
||||
*.py)
|
||||
# 看 diff 是否新增 import
|
||||
local diff
|
||||
if [ "$MODE" = "staged" ]; then
|
||||
diff=$(git diff --cached "$file")
|
||||
else
|
||||
diff=$(git diff "$BASE_BRANCH"...HEAD -- "$file")
|
||||
fi
|
||||
local new_imports
|
||||
new_imports=$(echo "$diff" | grep -E '^\+.*^(from|import) ' | grep -vE '^\+\s*#' | head -10)
|
||||
if [ -z "$new_imports" ]; then return 0; fi
|
||||
|
||||
# 提取第三方包(标准库除外)
|
||||
local third_party
|
||||
third_party=$(echo "$new_imports" | grep -E '^\+.*^(from|import) [a-z_]+' | \
|
||||
sed -E 's/^\+ *(from|import) ([a-z_][a-z0-9_]*).*/\2/' | \
|
||||
grep -vE '^(os|sys|re|json|time|datetime|typing|asyncio|pathlib|hashlib|hmac|secrets|base64|urllib|http|logging|functools|collections|itertools|contextlib|io|copy|enum|dataclasses|abc|math|random|string|subprocess|threading|multiprocessing|signal|socket|ssl|tempfile|shutil|glob|fnmatch|stat|argparse|getopt|unittest|traceback|warnings|pickle|csv|xml|html|email|zoneinfo|decimal|fractions|gcd)' | \
|
||||
sort -u)
|
||||
if [ -n "$third_party" ]; then
|
||||
# 检查 requirements.txt 是否已有
|
||||
local missing=""
|
||||
for pkg in $third_party; do
|
||||
if ! grep -qiE "^${pkg}([=<>!~]|$)" backend/requirements.txt 2>/dev/null; then
|
||||
missing+="$pkg "
|
||||
fi
|
||||
done
|
||||
if [ -n "$missing" ]; then
|
||||
fail " $file: 新增第三方 import 但 requirements.txt 缺: $missing"
|
||||
else
|
||||
pass " $file: 新增 import 已在 requirements.txt"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*.ts|*.tsx|*.vue|*.js|*.jsx)
|
||||
# 看 diff 是否新增 import / require
|
||||
local diff
|
||||
if [ "$MODE" = "staged" ]; then
|
||||
diff=$(git diff --cached "$file")
|
||||
else
|
||||
diff=$(git diff "$BASE_BRANCH"...HEAD -- "$file")
|
||||
fi
|
||||
local new_imports
|
||||
new_imports=$(echo "$diff" | grep -E '^\+.*(import .* from |require\()' | head -10)
|
||||
if [ -z "$new_imports" ]; then return 0; fi
|
||||
|
||||
# 找对应 package.json
|
||||
local pkg_json="package.json"
|
||||
case "$file" in
|
||||
frontend-admin/*) pkg_json="frontend-admin/package.json" ;;
|
||||
frontend-agent/*) pkg_json="frontend-agent/package.json" ;;
|
||||
frontend-h5/*) pkg_json="frontend-h5/package.json" ;;
|
||||
frontend-portal/*) pkg_json="frontend-portal/package.json" ;;
|
||||
esac
|
||||
if [ -f "$pkg_json" ]; then
|
||||
# 提取包名(简单粗暴,workbuddy 改的常规 npm 包)
|
||||
local new_pkgs
|
||||
new_pkgs=$(echo "$new_imports" | grep -oE "from ['\"](@?[a-z][a-z0-9_/.-]+)" | sed -E "s/from ['\"]//" | sort -u)
|
||||
if [ -n "$new_pkgs" ]; then
|
||||
local missing=""
|
||||
for pkg in $new_pkgs; do
|
||||
if ! grep -qE "\"${pkg#@*/?}\"" "$pkg_json" 2>/dev/null; then
|
||||
missing+="$pkg "
|
||||
fi
|
||||
done
|
||||
if [ -n "$missing" ]; then
|
||||
warn " $file: 新增 import,需确认 $pkg_json 有: $missing"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
for f in $CHANGED; do
|
||||
[ -f "$f" ] && check_deps "$f"
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# 检查 3: alembic
|
||||
# =============================================================================
|
||||
info ""
|
||||
info "── 检查 3/4: alembic 迁移"
|
||||
|
||||
check_alembic() {
|
||||
local file="$1"
|
||||
# model 改了 → 必须有 alembic 迁移
|
||||
case "$file" in
|
||||
backend/app/models/*.py)
|
||||
# 看 diff 是否改 schema(Column / type / nullable / default)
|
||||
local diff
|
||||
if [ "$MODE" = "staged" ]; then
|
||||
diff=$(git diff --cached "$file")
|
||||
else
|
||||
diff=$(git diff "$BASE_BRANCH"...HEAD -- "$file")
|
||||
fi
|
||||
if echo "$diff" | grep -qE '^\+.*Column\(|^\+.*Mapped\['; then
|
||||
# 找本次 commit/branch 是否新增 alembic 迁移
|
||||
local migrations
|
||||
if [ "$MODE" = "staged" ]; then
|
||||
migrations=$(git diff --cached --name-only | grep "alembic/versions/.*\.py" || true)
|
||||
else
|
||||
migrations=$(git diff --name-only "$BASE_BRANCH"...HEAD | grep "alembic/versions/.*\.py" || true)
|
||||
fi
|
||||
if [ -z "$migrations" ]; then
|
||||
fail " $file: model schema 变化但无 alembic 迁移"
|
||||
else
|
||||
pass " $file: model 改了,有 alembic 迁移: $migrations"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
backend/alembic/versions/*.py)
|
||||
info " $file: alembic 迁移新增"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
for f in $CHANGED; do
|
||||
[ -f "$f" ] && check_alembic "$f"
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# 检查 4: 配置
|
||||
# =============================================================================
|
||||
info ""
|
||||
info "── 检查 4/4: 配置 (nginx / docker / conf)"
|
||||
|
||||
check_config() {
|
||||
local file="$1"
|
||||
case "$file" in
|
||||
nginx.conf|deploy-server/nginx.conf|docker-compose.yml|docker-compose*.yml|.env.example)
|
||||
warn " $file: 配置文件改动,确认 deploy.sh / docs/DEPLOY_NAS.md 同步更新"
|
||||
;;
|
||||
backend/app/config.py)
|
||||
# 配置改了 → 看 .env.example 是否同步
|
||||
local diff
|
||||
if [ "$MODE" = "staged" ]; then
|
||||
diff=$(git diff --cached "$file")
|
||||
else
|
||||
diff=$(git diff "$BASE_BRANCH"...HEAD -- "$file")
|
||||
fi
|
||||
local new_settings
|
||||
new_settings=$(echo "$diff" | grep -E '^\+.*(os\.getenv|Field\(.*env=)' | head -5)
|
||||
if [ -n "$new_settings" ]; then
|
||||
warn " $file: 新增配置项,确认 .env.example 同步"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
for f in $CHANGED; do
|
||||
[ -f "$f" ] && check_config "$f"
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# 总结
|
||||
# =============================================================================
|
||||
info ""
|
||||
info "── 总结"
|
||||
echo " PASS: $PASS_COUNT / WARN: $WARN_COUNT / FAIL: $FAIL_COUNT"
|
||||
|
||||
if [ $FAIL_COUNT -gt 0 ]; then
|
||||
echo ""
|
||||
echo "🛑 [FAIL] 列表:"
|
||||
for msg in "${FAIL_LIST[@]}"; do echo " - $msg"; done
|
||||
echo ""
|
||||
echo "🛑 预检失败,请修 FAIL 项后再推送"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ $WARN_COUNT -gt 0 ]; then
|
||||
echo ""
|
||||
echo "⚠️ [WARN] 列表:"
|
||||
for msg in "${WARN_LIST[@]}"; do echo " - $msg"; done
|
||||
if [ "$STRICT" = true ]; then
|
||||
echo ""
|
||||
echo "🛑 严格模式下 WARN 也算失败"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
echo "⚠️ 有 WARN 项,但允许推送(评审员关注)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ 预检通过,可以推送"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user