fix: 修复会话鉴权「登录后 token 无效」并补齐 NAS 部署流程
- deps.py: _create_redis_client 增加同步 ping 校验,Redis 不可达时正确降级为内存模式 - main.py: lifespan 复用 deps 的 UserStore 单例,避免 admin 与 API 请求实例不一致 - 前端: 强制改密弹窗(must_change_password 用户)、兼容新旧登录返回格式、markPasswordChanged - 新增 NAS SSH 部署脚本与批处理测试;gitignore 前端构建产物
This commit is contained in:
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/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
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# 安装本地 git pre-push 门禁
|
||||
#
|
||||
# 作用:任何「推送到 origin(NAS)」的操作前,先跑后端测试;若前端有变化,
|
||||
# 先在本地预构建一次。任一环节失败则阻断推送,确保坏代码绝不上线。
|
||||
#
|
||||
# 说明:
|
||||
# - deploy.sh 推送前会设 QMDS_SKIP_PREPUSH=1,避免与自身校验重复跑。
|
||||
# - 仅对 origin 远端生效,推其他远端不受影响。
|
||||
#
|
||||
# 卸载:rm .git/hooks/pre-push
|
||||
# =============================================================================
|
||||
set -e
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
HOOK="$ROOT/.git/hooks/pre-push"
|
||||
mkdir -p "$(dirname "$HOOK")"
|
||||
|
||||
cat > "$HOOK" <<'HOOK_EOF'
|
||||
#!/usr/bin/env bash
|
||||
# QMDSearch pre-push 校验门禁(由 scripts/install-hooks.sh 安装)
|
||||
REMOTE="$1"
|
||||
# 仅对 NAS 远端 (origin) 做校验;且 deploy.sh 已设 QMDS_SKIP_PREPUSH 时跳过
|
||||
if [[ "$REMOTE" != "origin" ]]; then exit 0; fi
|
||||
if [[ -n "${QMDS_SKIP_PREPUSH:-}" ]]; then exit 0; fi
|
||||
|
||||
echo "[pre-push] 推送前校验后端测试..."
|
||||
if ! uv run pytest -q --no-header 2>&1 | tail -n 20; then
|
||||
echo "[pre-push] 测试失败,已阻断推送。请先修复后再 push。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 暂存区若含前端变化,本地先预构建一次,避免推到 NAS 才翻车
|
||||
if git diff --cached --name-only | grep -qE '^frontend/'; then
|
||||
echo "[pre-push] 检测到前端变化,本地预构建..."
|
||||
if ! (cd frontend && npm ci --no-audit --no-fund && npm run build); then
|
||||
echo "[pre-push] 前端构建失败,已阻断推送。" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[pre-push] 校验通过,允许推送。"
|
||||
HOOK_EOF
|
||||
|
||||
chmod +x "$HOOK"
|
||||
echo "已安装 pre-push 门禁 -> $HOOK"
|
||||
echo "如需卸载: rm $HOOK"
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# QMDSearch NAS 端部署(通过 SSH 直连执行,密钥免密)
|
||||
#
|
||||
# 环境约束(已实测):
|
||||
# - NAS 上有 docker compose v2,但【没有 node/npm】→ 前端必须在本地构建后 rsync 过去
|
||||
# - compose 的 app 服务只有 image: 无 build: → 依赖变化用 `docker build -t qmdsearch-app:latest .`
|
||||
# - kp 已在 docker 组,可免 sudo 跑 docker
|
||||
# - 部署目录 /vol1/qmdsearch 是 git 工作树,以 origin/main 为唯一真相源
|
||||
#
|
||||
# 流程:
|
||||
# 1. SSH 到 NAS:git fetch + git reset --hard origin/main(同步受管代码),并算出变更范围
|
||||
# 2. 本地:若前端变化 → 本地 npm 构建 → rsync frontend/dist 到 NAS(NAS 无 node)
|
||||
# 3. SSH 到 NAS:依赖变化则 docker build + force-recreate;否则 restart app
|
||||
# 4. 健康检查 /health;失败回滚到上一稳定提交
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/nas-deploy-ssh.sh 正式部署
|
||||
# ./scripts/nas-deploy-ssh.sh --dry-run 只预览将要部署的变更,不重启
|
||||
# 环境变量:NAS_HOST(默认 nas) DEPLOY_DIR(默认 /vol1/qmdsearch) APP_PORT(默认 8000)
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
DEPLOY_HOST="${NAS_HOST:-nas}"
|
||||
DEPLOY_DIR="${DEPLOY_DIR:-/vol1/qmdsearch}"
|
||||
APP_PORT="${APP_PORT:-8000}"
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DRY_RUN=0
|
||||
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=1
|
||||
|
||||
# 前置检查
|
||||
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$DEPLOY_HOST" true 2>/dev/null; then
|
||||
echo "[local] 无法免密 SSH 到 $DEPLOY_HOST,请先配置 SSH 密钥登录。" >&2
|
||||
exit 1
|
||||
fi
|
||||
command -v rsync >/dev/null 2>&1 || { echo "[local] 本机缺少 rsync,请先安装。" >&2; exit 1; }
|
||||
|
||||
# ---- Phase 1: NAS 同步受管代码 + 计算变更范围 ----
|
||||
SCOPE=$(ssh "$DEPLOY_HOST" "bash -s -- '${DEPLOY_DIR}'" <<'REMOTE'
|
||||
DEPLOY_DIR="$1"
|
||||
set -uo pipefail
|
||||
cd "$DEPLOY_DIR" || { echo "NOCD"; exit 1; }
|
||||
git fetch origin main 2>&1 | tail -n 2
|
||||
NEW=$(git rev-parse origin/main)
|
||||
PREV=$(cat .last_deployed_commit 2>/dev/null || echo "NONE")
|
||||
if [[ "$PREV" == "$NEW" ]]; then echo "UP_TO_DATE"; exit 0; fi
|
||||
OLD_HEAD=$(git rev-parse HEAD)
|
||||
git reset --hard origin/main 2>&1 | tail -n 2
|
||||
BASE="$OLD_HEAD"
|
||||
[[ "$PREV" != "NONE" ]] && BASE="$PREV"
|
||||
CHANGED=$(git diff --name-only "$BASE" "$NEW")
|
||||
FE=0; DEPS=0
|
||||
echo "$CHANGED" | grep -qE '^frontend/(src/|public/|index\.html|package\.json|vite\.config|package-lock\.json)' && FE=1
|
||||
echo "$CHANGED" | grep -qE '^(pyproject\.toml|uv\.lock|Dockerfile)' && DEPS=1
|
||||
echo "SCOPE FE=$FE DEPS=$DEPS PREV=$PREV NEW=$NEW"
|
||||
REMOTE
|
||||
)
|
||||
|
||||
if echo "$SCOPE" | grep -q "NOCD"; then echo "[nas] 无法进入 $DEPLOY_DIR"; exit 1; fi
|
||||
if echo "$SCOPE" | grep -q "UP_TO_DATE"; then echo "[nas] 已是最新,无需更新。"; exit 0; fi
|
||||
|
||||
FE=$(echo "$SCOPE" | grep -oE 'FE=[01]' | cut -d= -f2)
|
||||
DEPS=$(echo "$SCOPE" | grep -oE 'DEPS=[01]' | cut -d= -f2)
|
||||
PREV=$(echo "$SCOPE" | grep -oE 'PREV=[0-9a-f]+' | cut -d= -f2)
|
||||
NEW=$(echo "$SCOPE" | grep -oE 'NEW=[0-9a-f]+' | cut -d= -f2)
|
||||
echo "[deploy] 将部署 $PREV -> $NEW (FE=$FE DEPS=$DEPS)"
|
||||
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
echo "[deploy][dry-run] 变更文件:"
|
||||
if [[ "$PREV" != "NONE" ]]; then
|
||||
ssh "$DEPLOY_HOST" "cd $DEPLOY_DIR && git diff --name-only $PREV $NEW" 2>/dev/null | sed 's/^/ /'
|
||||
else
|
||||
echo " (首次部署,将全量同步受管代码)"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---- Phase 2: 前端本地构建 + rsync 到 NAS(NAS 无 node)----
|
||||
if [[ "$FE" == "1" ]]; then
|
||||
echo "[local] 本地构建前端..."
|
||||
if ! (cd "$ROOT/frontend" && npm ci --no-audit --no-fund && npm run build); then
|
||||
echo "[local][WARN] 前端构建失败(当前环境可能受限或缺少 node 依赖),跳过前端同步;后端仍会部署,但 frontend/dist 可能为旧版本。待在可正常构建的环境中重跑即补齐。"
|
||||
else
|
||||
echo "[local] rsync frontend/dist -> $DEPLOY_HOST:$DEPLOY_DIR/frontend/dist"
|
||||
if ! rsync -az --delete "$ROOT/frontend/dist/" "$DEPLOY_HOST:$DEPLOY_DIR/frontend/dist/"; then
|
||||
echo "[local][WARN] rsync 失败,跳过前端同步。"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- Phase 3: NAS 容器部署 + 健康检查 ----
|
||||
ssh "$DEPLOY_HOST" "bash -s -- '$DEPLOY_DIR' '$APP_PORT' '$DEPS' '$NEW' '$PREV'" <<'REMOTE'
|
||||
DEPLOY_DIR="$1"; APP_PORT="$2"; DEPS="$3"; NEW="$4"; PREV="$5"
|
||||
set -uo pipefail
|
||||
cd "$DEPLOY_DIR"
|
||||
if [[ "$DEPS" == "1" ]]; then
|
||||
echo "[nas] 依赖变化,重建镜像: docker build -t qmdsearch-app:latest ."
|
||||
if ! docker build -t qmdsearch-app:latest . 2>&1 | tail -n 20; then
|
||||
echo "[nas] 镜像构建失败,回滚到 $PREV"; git checkout -f "$PREV"; exit 1
|
||||
fi
|
||||
echo "[nas] 用新镜像重建容器: docker compose up -d --force-recreate app"
|
||||
docker compose up -d --force-recreate app 2>&1 | tail -n 10
|
||||
else
|
||||
echo "[nas] 仅代码/前端变化,重启 app: docker compose restart app"
|
||||
docker compose restart app 2>&1 | tail -n 5
|
||||
fi
|
||||
echo "[nas] 健康检查 http://localhost:$APP_PORT/api/v1/health ..."
|
||||
for i in $(seq 1 20); do
|
||||
if curl -fsS "http://localhost:$APP_PORT/api/v1/health" >/dev/null 2>&1; then
|
||||
echo "$NEW" > .last_deployed_commit
|
||||
echo "[nas] 健康检查通过 ✓ 部署完成"
|
||||
exit 0
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
echo "[nas] 健康检查失败,回滚到 $PREV"
|
||||
git checkout -f "$PREV"
|
||||
docker compose restart app
|
||||
exit 1
|
||||
REMOTE
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# QMDSearch NAS 端自动部署钩子 (post-receive)
|
||||
#
|
||||
# 这是真正的「持续部署(CD)」:当本地 deploy.sh 把代码 push 到 NAS 后,
|
||||
# 此钩子自动完成 checkout → 按需构建 → 重启/重建容器 → 健康检查 → 失败回滚。
|
||||
#
|
||||
# 安装方式(二选一):
|
||||
# A. 作为 bare 仓库的 hook(推荐,全自动):
|
||||
# # 假设 NAS 上 bare 仓库在 /volume1/git/QMDSearch.git,工作树在 /volume1/docker/QMDSearch
|
||||
# cp scripts/nas-post-receive /volume1/git/QMDSearch.git/hooks/post-receive
|
||||
# chmod +x /volume1/git/QMDSearch.git/hooks/post-receive
|
||||
# # 在该 bare 仓库 config 中设置:
|
||||
# # [core] bare = true
|
||||
# # [hooks] 并确保工作树目录已 clone 过一次
|
||||
# # 首次需手动:git --work-tree=/volume1/docker/QMDSearch --git-dir=/volume1/git/QMDSearch.git checkout -f
|
||||
#
|
||||
# B. 作为普通脚本手动触发(无 bare 仓库时):
|
||||
# cd /volume1/docker/QMDSearch && git pull && sudo bash scripts/nas-post-receive
|
||||
#
|
||||
# 前置依赖(NAS 上需具备):git、docker、docker compose、node/npm
|
||||
# 可调环境变量:DEPLOY_DIR(工作树)、APP_PORT、LOG_FILE
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
DEPLOY_DIR="${DEPLOY_DIR:-/volume1/docker/QMDSearch}" # ← 改成你的 NAS 部署目录
|
||||
GIT_DIR="$DEPLOY_DIR/.git"
|
||||
LOG_FILE="${LOG_FILE:-$DEPLOY_DIR/deploy.log}"
|
||||
APP_PORT="${APP_PORT:-8000}"
|
||||
PREV_COMMIT_FILE="$DEPLOY_DIR/.last_deployed_commit"
|
||||
|
||||
log(){ printf '%s [nas-deploy] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE"; }
|
||||
|
||||
rollback(){
|
||||
log "回滚到上一稳定提交: ${PREV:-<无>}"
|
||||
if [[ -n "${PREV:-}" ]]; then
|
||||
git --work-tree="$DEPLOY_DIR" --git-dir="$GIT_DIR" checkout -f "$PREV"
|
||||
(cd "$DEPLOY_DIR" && docker compose restart app) | tee -a "$LOG_FILE"
|
||||
echo "$PREV" > "$PREV_COMMIT_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# 解析 post-receive 通过 stdin 传入的 <old> <new> <ref>
|
||||
newrev=""; refname=""
|
||||
while read -r oldrev newrev refname; do :; done
|
||||
# 仅处理 main 分支的推送;手动运行时 newrev 为空,回退到 HEAD
|
||||
if [[ -z "$newrev" ]]; then
|
||||
newrev="$(git --git-dir="$GIT_DIR" rev-parse HEAD 2>/dev/null || echo "")"
|
||||
fi
|
||||
if [[ -n "$refname" && "$refname" != "refs/heads/main" ]]; then
|
||||
log "忽略非 main 分支推送 ($refname)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "=== 开始部署 (newrev=${newrev:0:8}) ==="
|
||||
|
||||
# 1) 检出到工作树
|
||||
git --work-tree="$DEPLOY_DIR" --git-dir="$GIT_DIR" checkout -f
|
||||
cd "$DEPLOY_DIR" || { log "无法进入 $DEPLOY_DIR"; exit 1; }
|
||||
|
||||
# 2) 计算变化范围
|
||||
PREV="$(cat "$PREV_COMMIT_FILE" 2>/dev/null || echo "")"
|
||||
if [[ -n "$PREV" && -n "$newrev" ]]; then
|
||||
CHANGED="$(git --git-dir="$GIT_DIR" diff --name-only "$PREV" "$newrev")"
|
||||
else
|
||||
CHANGED="$(git --git-dir="$GIT_DIR" show --name-only --format= "$newrev")"
|
||||
fi
|
||||
|
||||
has(){ echo "$CHANGED" | grep -qE "$1"; }
|
||||
FE_CHANGED=0; DEPS_CHANGED=0
|
||||
has '^frontend/(src/|public/|index\.html|package\.json|vite\.config|package-lock\.json)' && FE_CHANGED=1
|
||||
has '^(pyproject\.toml|uv\.lock|Dockerfile)' && DEPS_CHANGED=1
|
||||
log "变更范围 → frontend=$FE_CHANGED deps=$DEPS_CHANGED"
|
||||
|
||||
# 3) 按需构建前端(dist 不入库,必须在此构建)
|
||||
if [[ "$FE_CHANGED" -eq 1 ]]; then
|
||||
log "检测前端变化,重新构建..."
|
||||
if ! (cd frontend && npm ci --no-audit --no-fund && npm run build) 2>&1 | tee -a "$LOG_FILE"; then
|
||||
log "前端构建失败,中止部署(保留上一版本)"; exit 1
|
||||
fi
|
||||
ok(){ :; }; log "前端构建完成"
|
||||
fi
|
||||
|
||||
# 4) 部署容器
|
||||
if [[ "$DEPS_CHANGED" -eq 1 ]]; then
|
||||
log "依赖变化,重建镜像并重启: docker compose up -d --build app"
|
||||
docker compose up -d --build app 2>&1 | tee -a "$LOG_FILE"
|
||||
else
|
||||
log "仅代码变化,重启 app 服务: docker compose restart app"
|
||||
docker compose restart app 2>&1 | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# 5) 健康检查(失败自动回滚)
|
||||
log "健康检查 http://localhost:$APP_PORT/health ..."
|
||||
for i in $(seq 1 15); do
|
||||
if curl -fsS "http://localhost:$APP_PORT/health" >/dev/null 2>&1; then
|
||||
log "健康检查通过 ✓"
|
||||
[[ -n "$newrev" ]] && echo "$newrev" > "$PREV_COMMIT_FILE"
|
||||
log "=== 部署完成 ==="
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
log "健康检查失败,触发回滚"
|
||||
rollback
|
||||
exit 1
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# QMDSearch 文件变化监视器(纯轮询,无需 fswatch / inotifywait)
|
||||
#
|
||||
# 作用:持续监听仓库代码变化,实现「每次更新代码后自动检测」。
|
||||
# - 默认模式:检测到变化 → 本地跑测试 + 前端预构建 → 提示你运行 deploy.sh
|
||||
# - --auto 模式:检测到变化 → 直接调用 ./scripts/deploy.sh 全自动部署
|
||||
# (注意:--auto 会在保存即提交推送,慎用,建议确认改动完整后再用)
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/watch.sh 监听并本地校验,提示部署
|
||||
# ./scripts/watch.sh --auto 监听变化后全自动部署
|
||||
# ./scripts/watch.sh --interval 5 轮询间隔秒数(默认 3)
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
AUTO=0
|
||||
INTERVAL=3
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--auto) AUTO=1 ;;
|
||||
--interval) INTERVAL="${2:-3}"; shift ;;
|
||||
*) echo "未知参数: $1"; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
SNAPSHOT="$(mktemp)"
|
||||
record(){ git status --porcelain > "$SNAPSHOT"; }
|
||||
record
|
||||
|
||||
if [[ "$AUTO" -eq 1 ]]; then
|
||||
echo "[watch] 监听 $ROOT(AUTO 模式:变化即部署,间隔 ${INTERVAL}s,Ctrl-C 退出)"
|
||||
else
|
||||
echo "[watch] 监听 $ROOT(校验模式:变化即本地校验并提示,间隔 ${INTERVAL}s,Ctrl-C 退出)"
|
||||
fi
|
||||
|
||||
while true; do
|
||||
sleep "$INTERVAL"
|
||||
if ! git status --porcelain | diff -q - "$SNAPSHOT" >/dev/null 2>&1; then
|
||||
echo "[watch] $(date '+%H:%M:%S') 检测到代码变化"
|
||||
if [[ "$AUTO" -eq 1 ]]; then
|
||||
bash "$ROOT/scripts/deploy.sh"
|
||||
else
|
||||
echo "[watch] 运行本地校验..."
|
||||
uv run pytest -q --no-header 2>&1 | tail -n 10 || true
|
||||
if git status --porcelain | awk '{print $2}' | grep -qE '^frontend/'; then
|
||||
echo "[watch] 前端变化,本地预构建..."
|
||||
(cd frontend && npm ci --no-audit --no-fund && npm run build) 2>&1 | tail -n 5 || true
|
||||
fi
|
||||
echo "[watch] 校验完成。确认无误后运行: ./scripts/deploy.sh"
|
||||
fi
|
||||
record
|
||||
fi
|
||||
done
|
||||
Reference in New Issue
Block a user