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
+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
|
||||
Reference in New Issue
Block a user