bdd30f0a88
- deps.py: _create_redis_client 增加同步 ping 校验,Redis 不可达时正确降级为内存模式 - main.py: lifespan 复用 deps 的 UserStore 单例,避免 admin 与 API 请求实例不一致 - 前端: 强制改密弹窗(must_change_password 用户)、兼容新旧登录返回格式、markPasswordChanged - 新增 NAS SSH 部署脚本与批处理测试;gitignore 前端构建产物
145 lines
5.7 KiB
Bash
Executable File
145 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# =============================================================================
|
||
# QMDSearch 自动部署脚本(本地 CI/CD 入口)
|
||
#
|
||
# 设计原则(贴合 NAS 自托管 + 挂载式部署):
|
||
# 1. 检测变化 —— 对比 origin/main,区分 backend / frontend / deps 范围
|
||
# 2. 校验 —— 本地跑 pytest(CI 门禁),坏代码绝不上线
|
||
# 3. 按需构建 —— 仅前端变化时本地预构建(dist 被 gitignore,NAS 端会再构建)
|
||
# 4. 提交推送 —— git commit + push origin main,推送后 SSH 到 NAS 执行真正部署
|
||
#
|
||
# 用法:
|
||
# ./scripts/deploy.sh 正常流程:检测 → 校验 → 按需构建 → 提交推送 → SSH 到 NAS 部署
|
||
# ./scripts/deploy.sh --dry 零副作用自测:只 检测/校验/构建,不提交不推送
|
||
# ./scripts/deploy.sh --no-push 提交但不推送(准备好后手动 push 或再跑一次)
|
||
# ./scripts/deploy.sh --no-ssh 推送后不自动 SSH 部署(仅同步代码到 NAS git)
|
||
# ./scripts/deploy.sh --skip-tests 跳过 pytest(谨慎)
|
||
# ./scripts/deploy.sh --skip-fe 跳过前端构建(即便前端变化)
|
||
# ./scripts/deploy.sh --build-fe 强制重新构建前端
|
||
# ./scripts/deploy.sh --help
|
||
# =============================================================================
|
||
set -euo pipefail
|
||
|
||
REMOTE="origin"
|
||
BRANCH="main"
|
||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
cd "$ROOT"
|
||
|
||
NO_PUSH=0
|
||
DRY=0
|
||
NO_SSH=0
|
||
SKIP_TESTS=0
|
||
SKIP_FE=0
|
||
FORCE_FE=0
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--no-push) NO_PUSH=1 ;;
|
||
--dry) DRY=1 ;;
|
||
--no-ssh) NO_SSH=1 ;;
|
||
--skip-tests) SKIP_TESTS=1 ;;
|
||
--skip-fe) SKIP_FE=1 ;;
|
||
--build-fe) FORCE_FE=1 ;;
|
||
--help|-h) sed -n '1,22p' "$0"; exit 0 ;;
|
||
*) echo "未知参数: $1"; exit 2 ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
log(){ printf '\033[36m[deploy]\033[0m %s\n' "$*"; }
|
||
ok(){ printf '\033[32m[ ok ]\033[0m %s\n' "$*"; }
|
||
err(){ printf '\033[31m[err ]\033[0m %s\n' "$*" >&2; }
|
||
|
||
# ---- 1. 变化检测 --------------------------------------------------------------
|
||
log "同步远端引用..."
|
||
git fetch --quiet "$REMOTE" "$BRANCH" 2>/dev/null || log "fetch 失败(离线?继续本地流程)"
|
||
|
||
LOCAL_CHANGES="$(git status --porcelain)"
|
||
AHEAD="$(git rev-list --count "$REMOTE/$BRANCH..HEAD" 2>/dev/null || echo 0)"
|
||
|
||
# 汇总「已提交未推送」+「未提交」的变更文件,去重
|
||
CHANGED_FILES="$(
|
||
{ git diff --name-only "$REMOTE/$BRANCH" HEAD 2>/dev/null; echo "$LOCAL_CHANGES" | awk '{print $2}'; } \
|
||
| grep -v '^$' | sort -u
|
||
)"
|
||
|
||
has(){ echo "$CHANGED_FILES" | grep -qE "$1"; }
|
||
|
||
BACKEND_CHANGED=0; FRONTEND_CHANGED=0; DEPS_CHANGED=0
|
||
if has '^(app/|scripts/|tests/|.*\.py$|Dockerfile|docker-compose\.yml|\.env\.example)'; then BACKEND_CHANGED=1; fi
|
||
if has '^frontend/(src/|public/|index\.html|package\.json|vite\.config|package-lock\.json)'; then FRONTEND_CHANGED=1; fi
|
||
if has '^(pyproject\.toml|uv\.lock|Dockerfile)'; then DEPS_CHANGED=1; fi
|
||
|
||
if [[ -z "$CHANGED_FILES" && "$AHEAD" -eq 0 ]]; then
|
||
ok "无代码变化,无需部署。"
|
||
exit 0
|
||
fi
|
||
log "变更范围 → backend=$BACKEND_CHANGED frontend=$FRONTEND_CHANGED deps=$DEPS_CHANGED (ahead=$AHEAD)"
|
||
|
||
# ---- 2. 校验(本地 CI gate)---------------------------------------------------
|
||
if [[ "$SKIP_TESTS" -eq 0 && "$BACKEND_CHANGED" -eq 1 ]]; then
|
||
log "运行后端测试 (uv run pytest)..."
|
||
if ! uv run pytest -q --no-header 2>&1 | tail -n 30; then
|
||
err "测试失败,已中止部署。请修复后再运行 deploy.sh"
|
||
exit 1
|
||
fi
|
||
ok "后端测试通过"
|
||
# 顺带做一次非阻断的静态检查
|
||
if command -v uv >/dev/null 2>&1; then
|
||
log "运行 ruff 静态检查(不阻断)..."
|
||
uv run ruff check app tests 2>&1 | tail -n 15 || true
|
||
fi
|
||
fi
|
||
|
||
# ---- 3. 按需构建前端 ----------------------------------------------------------
|
||
if [[ ("$FRONTEND_CHANGED" -eq 1 || "$FORCE_FE" -eq 1) && "$SKIP_FE" -eq 0 ]]; then
|
||
log "构建前端 (npm ci && npm run build)..."
|
||
if ! (cd frontend && npm ci --no-audit --no-fund && npm run build); then
|
||
err "前端构建失败,已中止。"
|
||
exit 1
|
||
fi
|
||
ok "前端构建完成 -> frontend/dist"
|
||
log "提示: frontend/dist 已被 gitignore,NAS 端 post-receive 会重新构建以生效。"
|
||
fi
|
||
|
||
# ---- 4. 提交 + 推送 -----------------------------------------------------------
|
||
if [[ "$DRY" -eq 1 ]]; then
|
||
log "(--dry) 零副作用模式:跳过提交与推送。以上为检测结果,确认无误后可去掉 --dry 正式部署。"
|
||
exit 0
|
||
fi
|
||
|
||
if [[ -n "$LOCAL_CHANGES" ]]; then
|
||
log "暂存并提交本地变化..."
|
||
git add -A # .gitignore 已排除 .env / data / node_modules / .workbuddy / .trae 等
|
||
SCOPE=""
|
||
[[ "$BACKEND_CHANGED" -eq 1 ]] && SCOPE="$SCOPE backend"
|
||
[[ "$FRONTEND_CHANGED" -eq 1 ]] && SCOPE="$SCOPE frontend"
|
||
[[ "$DEPS_CHANGED" -eq 1 ]] && SCOPE="$SCOPE deps"
|
||
MSG="chore(deploy): 自动部署 $(date '+%Y-%m-%d %H:%M')${SCOPE:+ |$SCOPE}"
|
||
git commit -q -m "$MSG"
|
||
ok "已提交: $MSG"
|
||
fi
|
||
|
||
if [[ "$NO_PUSH" -eq 1 ]]; then
|
||
log "(--no-push) 未推送。当前领先远端 $AHEAD 个提交,确认无误后去掉 --no-push 再跑。"
|
||
exit 0
|
||
fi
|
||
|
||
export QMDS_SKIP_PREPUSH=1 # 避免与 pre-push hook 重复跑测试
|
||
log "推送到 $REMOTE/$BRANCH(触发 NAS 自动部署)..."
|
||
if ! git push "$REMOTE" "$BRANCH"; then
|
||
err "推送失败:可能远端有更新,请先 git pull 解决冲突后重试。"
|
||
exit 1
|
||
fi
|
||
ok "已推送。代码已同步至 NAS git。"
|
||
if [[ "$NO_SSH" -eq 0 ]]; then
|
||
log "通过 SSH 在 NAS 上执行部署..."
|
||
if bash "$ROOT/scripts/nas-deploy-ssh.sh"; then
|
||
ok "NAS 部署完成。"
|
||
else
|
||
err "NAS 部署脚本返回非 0(详见上方 NAS 输出)。"
|
||
exit 1
|
||
fi
|
||
else
|
||
log "(--no-ssh) 已跳过 NAS 端部署。如需部署请运行: ./scripts/nas-deploy-ssh.sh"
|
||
fi
|