feat: deploy.sh 交互进度 — [n/11]阶段标题/长命令心跳/部署预览+y/N确认(--yes跳过)
This commit is contained in:
+169
-75
@@ -5,6 +5,7 @@
|
|||||||
# 用法:
|
# 用法:
|
||||||
# bash deploy/deploy.sh # 正式部署(在服务器 /root/scilit 下执行)
|
# bash deploy/deploy.sh # 正式部署(在服务器 /root/scilit 下执行)
|
||||||
# bash deploy/deploy.sh --dry-run # 只打印将执行的命令,不执行任何变更
|
# bash deploy/deploy.sh --dry-run # 只打印将执行的命令,不执行任何变更
|
||||||
|
# bash deploy/deploy.sh --yes # 跳过部署前 y/N 确认提示
|
||||||
# bash deploy/deploy.sh --help
|
# bash deploy/deploy.sh --help
|
||||||
#
|
#
|
||||||
# 前置条件:
|
# 前置条件:
|
||||||
@@ -14,9 +15,14 @@
|
|||||||
#
|
#
|
||||||
# 流程(对应 docs/16 §2 设计):
|
# 流程(对应 docs/16 §2 设计):
|
||||||
# 遗留标记检查 → source .env → flock → 工作区校验 → 基础设施健康门 →
|
# 遗留标记检查 → source .env → flock → 工作区校验 → 基础设施健康门 →
|
||||||
# PREV_SHA → git pull → before alembic head → predeploy 快照 →
|
# PREV_SHA → git pull → destructive 判定 → 部署预览+确认 → 迁移前快照 →
|
||||||
# destructive 判定 → build + 双 tag → .pending-deploy → 迁移(退出码门控) →
|
# build + 双 tag → .pending-deploy → 迁移(退出码门控) → up -d --no-deps →
|
||||||
# after alembic head → up -d --no-deps → 健康轮询 → versions.log → 镜像治理
|
# 健康轮询 → versions.log → 镜像治理
|
||||||
|
#
|
||||||
|
# 交互与进度:
|
||||||
|
# · 每个阶段打印 [n/11] 标题 + 完成耗时,随时知道进行到哪
|
||||||
|
# · 静默长命令(快照/迁移/容器切换)带每秒心跳,不会"看起来假死"
|
||||||
|
# · 部署前打印预览并 y/N 确认(--yes 跳过;非交互终端自动继续)
|
||||||
#
|
#
|
||||||
# 失败处理分两段:
|
# 失败处理分两段:
|
||||||
# · 迁移前失败(快照/构建阶段)→ 容器仍是旧的,只清理中间态,不触发回滚
|
# · 迁移前失败(快照/构建阶段)→ 容器仍是旧的,只清理中间态,不触发回滚
|
||||||
@@ -34,12 +40,44 @@ BACKUP_DIR="${BACKUP_DIR:-/data/backups}"
|
|||||||
KEEP_TAGS=10 # 保留最近 N 个带 tag 版本镜像(§5 count 制)
|
KEEP_TAGS=10 # 保留最近 N 个带 tag 版本镜像(§5 count 制)
|
||||||
HEALTH_POLLS=12 # 健康轮询次数 × 5s = 60s 上限
|
HEALTH_POLLS=12 # 健康轮询次数 × 5s = 60s 上限
|
||||||
|
|
||||||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
|
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||||
info() { echo -e "${GREEN}[✓]${NC} $1"; }
|
info() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||||
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||||
error() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
|
error() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
|
||||||
|
|
||||||
DRY_RUN=0
|
# ── 进度框架:阶段计数 + 耗时 + 长命令心跳 ──
|
||||||
|
STAGE_TOTAL=11
|
||||||
|
STAGE_N=0
|
||||||
|
stage() {
|
||||||
|
STAGE_N=$((STAGE_N+1))
|
||||||
|
STAGE_START=$(date +%s)
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}════ [$STAGE_N/$STAGE_TOTAL] $1 ════${NC}"
|
||||||
|
}
|
||||||
|
stage_done() {
|
||||||
|
echo -e "${GREEN}──── 阶段 $STAGE_N 完成($(( $(date +%s) - STAGE_START ))s)${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
TICKER_PID=""
|
||||||
|
# 长命令心跳:每秒刷新耗时,防止 pg_dump / 迁移这类静默命令"看起来假死"
|
||||||
|
ticker_start() {
|
||||||
|
[ -t 1 ] || return 0 # 非 TTY(日志重定向)不跑心跳
|
||||||
|
[ "$DRY_RUN" -eq 1 ] && return 0
|
||||||
|
local msg="$1" t=0
|
||||||
|
( while :; do t=$((t+1)); printf "\r${YELLOW}%s … 已 %ss(无输出属正常)${NC}" "$msg" "$t"; sleep 1; done ) &
|
||||||
|
TICKER_PID=$!
|
||||||
|
}
|
||||||
|
ticker_stop() {
|
||||||
|
[ -n "$TICKER_PID" ] || return 0
|
||||||
|
kill "$TICKER_PID" 2>/dev/null || true
|
||||||
|
wait "$TICKER_PID" 2>/dev/null || true
|
||||||
|
TICKER_PID=""
|
||||||
|
printf "\r\033[K" # 清掉心跳行,避免与下条输出混行
|
||||||
|
}
|
||||||
|
cleanup() { ticker_stop; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
DRY_RUN=0; YES=0
|
||||||
# 所有变更命令统一走 run():--dry-run 下只打印不执行
|
# 所有变更命令统一走 run():--dry-run 下只打印不执行
|
||||||
run() {
|
run() {
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
@@ -51,9 +89,10 @@ run() {
|
|||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
用法: bash deploy/deploy.sh [--dry-run] [--help]
|
用法: bash deploy/deploy.sh [--dry-run] [--yes] [--help]
|
||||||
|
|
||||||
--dry-run 只打印将执行的命令,不执行任何变更(SHA 显示当前状态)
|
--dry-run 只打印将执行的命令,不执行任何变更(SHA 显示当前状态)
|
||||||
|
--yes 跳过部署前的 y/N 确认提示(非交互终端本就会自动继续)
|
||||||
--help 显示本帮助
|
--help 显示本帮助
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -61,28 +100,32 @@ EOF
|
|||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--dry-run) DRY_RUN=1 ;;
|
--dry-run) DRY_RUN=1 ;;
|
||||||
|
--yes) YES=1 ;;
|
||||||
--help) usage; exit 0 ;;
|
--help) usage; exit 0 ;;
|
||||||
*) usage; exit 1 ;;
|
*) usage; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# ── 1. 遗留 .pending-deploy 检测(I:上次部署中途崩溃的残留,不静默覆盖)──
|
# ═══ [1/11] 前置检查 ═══
|
||||||
|
stage "前置检查(遗留标记 / .env / 单实例锁 / 工作区)"
|
||||||
|
|
||||||
|
# 1. 遗留 .pending-deploy 检测(I:上次部署中途崩溃的残留,不静默覆盖)
|
||||||
if [ -f "$DEPLOY_DIR/.pending-deploy" ]; then
|
if [ -f "$DEPLOY_DIR/.pending-deploy" ]; then
|
||||||
error "⚠️ 上一次部署异常退出,残留 ${DEPLOY_DIR}/.pending-deploy。
|
error "⚠️ 上一次部署异常退出,残留 ${DEPLOY_DIR}/.pending-deploy。
|
||||||
请先确认当前状态(容器/迁移/versions.log)再继续,然后删除该标记或人工处理后重跑。"
|
请先确认当前状态(容器/迁移/versions.log)再继续,然后删除该标记或人工处理后重跑。"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 2. source .env(G:PG_PASSWORD/REDIS_PASSWORD 供 pg_dump 备选与探活)──
|
# 2. source .env(G:PG_PASSWORD/REDIS_PASSWORD 供 pg_dump 备选与探活)
|
||||||
if [ ! -f "$REPO_ROOT/.env" ]; then
|
if [ ! -f "$REPO_ROOT/.env" ]; then
|
||||||
error "未找到 ${REPO_ROOT}/.env(含密钥,不进 git)。请先在服务器配置。"
|
error "未找到 ${REPO_ROOT}/.env(含密钥,不进 git)。请先在服务器配置。"
|
||||||
fi
|
fi
|
||||||
set -a; source "$REPO_ROOT/.env"; set +a
|
set -a; source "$REPO_ROOT/.env"; set +a
|
||||||
|
|
||||||
# ── 3. flock 单实例锁(防两人/两终端并发部署)──
|
# 3. flock 单实例锁(防两人/两终端并发部署)
|
||||||
exec 9>/tmp/scilit-deploy.lock
|
exec 9>/tmp/scilit-deploy.lock
|
||||||
flock -n 9 || error "已有部署/回滚在进行中(/tmp/scilit-deploy.lock 被锁)。"
|
flock -n 9 || error "已有部署/回滚在进行中(/tmp/scilit-deploy.lock 被锁)。"
|
||||||
|
|
||||||
# ── 4. 工作区校验(防 hotfix 残留导致 git pull 冲突)──
|
# 4. 工作区校验(防 hotfix 残留导致 git pull 冲突)
|
||||||
# 只拦「已跟踪文件的改动」(这才是 pull 冲突来源);未跟踪文件(如服务器运维
|
# 只拦「已跟踪文件的改动」(这才是 pull 冲突来源);未跟踪文件(如服务器运维
|
||||||
# 工具 backup.sh/tmp_*.py)warn 不拦
|
# 工具 backup.sh/tmp_*.py)warn 不拦
|
||||||
STALE_CHANGES="$(git status --porcelain | grep -v '^??' || true)"
|
STALE_CHANGES="$(git status --porcelain | grep -v '^??' || true)"
|
||||||
@@ -95,8 +138,10 @@ if [ -n "$UNTRACKED" ]; then
|
|||||||
warn "存在未跟踪文件(不影响 pull,仅当新代码含同名文件时需注意):
|
warn "存在未跟踪文件(不影响 pull,仅当新代码含同名文件时需注意):
|
||||||
$UNTRACKED"
|
$UNTRACKED"
|
||||||
fi
|
fi
|
||||||
|
stage_done
|
||||||
|
|
||||||
# ── 5. 基础设施健康门(P14/N5:有 healthcheck 判 healthy、无则判 running)──
|
# ═══ [2/11] 基础设施健康门 ═══
|
||||||
|
stage "基础设施健康门(postgres/redis/elasticsearch/minio)"
|
||||||
infra_ok() {
|
infra_ok() {
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
echo "[DRY-RUN] 健康门:postgres/redis/elasticsearch/minio 模拟通过"
|
echo "[DRY-RUN] 健康门:postgres/redis/elasticsearch/minio 模拟通过"
|
||||||
@@ -120,64 +165,22 @@ infra_ok() {
|
|||||||
info "基础设施健康门通过"
|
info "基础设施健康门通过"
|
||||||
}
|
}
|
||||||
infra_ok
|
infra_ok
|
||||||
|
stage_done
|
||||||
|
|
||||||
# ── 6. 采 PREV_SHA(F:destructive 判定基线,必须在 git pull 之前)──
|
# ═══ [3/11] 拉取代码 ═══
|
||||||
|
stage "拉取代码(PREV_SHA → NEW_SHA)"
|
||||||
|
# PREV_SHA(F:destructive 判定基线,必须在 git pull 之前)
|
||||||
PREV_SHA="$(git rev-parse HEAD)"
|
PREV_SHA="$(git rev-parse HEAD)"
|
||||||
info "当前生产版本: $PREV_SHA"
|
info "当前生产版本: $PREV_SHA"
|
||||||
|
ticker_start "git pull(网络慢时可能卡住,属正常)"
|
||||||
# ── 7. git pull ──
|
|
||||||
run git pull
|
run git pull
|
||||||
|
ticker_stop
|
||||||
# ── 取 alembic 当前 revision ──
|
|
||||||
# 用 psql 直读 alembic_version(postgres 已健康门保证运行;容器内 socket trust 免密)。
|
|
||||||
# 不用 `run --rm backend alembic current`:before 阶段新镜像还没 build,image:latest
|
|
||||||
# 不存在会触发自动 build(打乱"快照在先"顺序)。表不存在(首次部署)返回空,v5 容错。
|
|
||||||
get_db_rev() {
|
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then echo "-"; return 0; fi
|
|
||||||
"${COMPOSE[@]}" exec -T postgres psql -U scilit -d scilit -tAc \
|
|
||||||
"SELECT version_num FROM alembic_version" 2>/dev/null | tr -d '[:space:]' | cut -c1-12
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── 8. before alembic head(空则记 '-',首次部署容错)──
|
|
||||||
ALEMBIC_BEFORE="$(get_db_rev || true)"
|
|
||||||
info "迁移前 alembic head: ${ALEMBIC_BEFORE:-(空/首次)}"
|
|
||||||
|
|
||||||
# ── 9. predeploy 全量快照(-Fc custom 格式,pg_restore 专用;含全部表)──
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
SNAPSHOT=""
|
|
||||||
snapshot_take() {
|
|
||||||
local ts
|
|
||||||
ts="$(date +%Y%m%d_%H%M%S)"
|
|
||||||
SNAPSHOT="$BACKUP_DIR/predeploy_$ts.dump"
|
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then
|
|
||||||
echo -e "${YELLOW}[DRY-RUN]${NC} pg_dump → $SNAPSHOT"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
# 容器内 pg_dump(不经宿主端口,与生产 backup.sh 同机制)
|
|
||||||
PGPASSWORD="${PG_PASSWORD}" "${COMPOSE[@]}" exec -T postgres \
|
|
||||||
pg_dump -Fc --no-owner --no-privileges -U scilit -d scilit > "$SNAPSHOT"
|
|
||||||
if [ ! -s "$SNAPSHOT" ]; then
|
|
||||||
rm -f "$SNAPSHOT"
|
|
||||||
error "predeploy 快照生成失败(空文件),中止部署。"
|
|
||||||
fi
|
|
||||||
info "predeploy 快照: $SNAPSHOT"
|
|
||||||
}
|
|
||||||
snapshot_take
|
|
||||||
|
|
||||||
# predeploy 快照保留最近 N=3(E1:count 判定 + sort | head -n -3 | xargs -r rm)
|
|
||||||
prune_snapshots() {
|
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then return 0; fi
|
|
||||||
local count
|
|
||||||
count="$(ls "$BACKUP_DIR"/predeploy_*.dump 2>/dev/null | wc -l)"
|
|
||||||
if [ "$count" -gt 3 ]; then
|
|
||||||
ls "$BACKUP_DIR"/predeploy_*.dump | sort | head -n -3 | xargs -r rm -f
|
|
||||||
info "已清理旧 predeploy 快照(保留最近 3 个)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
prune_snapshots
|
|
||||||
|
|
||||||
# ── 10. destructive 判定(P12 细化规则 + destructive_* 文件名/# DESTRUCTIVE 双保险)──
|
|
||||||
NEW_SHA="$(git rev-parse --short HEAD)"
|
NEW_SHA="$(git rev-parse --short HEAD)"
|
||||||
|
info "目标版本: $NEW_SHA"
|
||||||
|
stage_done
|
||||||
|
|
||||||
|
# ═══ [4/11] 迁移影响评估 ═══
|
||||||
|
stage "迁移影响评估(destructive 判定)"
|
||||||
detect_destructive() {
|
detect_destructive() {
|
||||||
local prev="$1" new="$2" files f fn
|
local prev="$1" new="$2" files f fn
|
||||||
files="$(git diff --name-only "$prev" "$new" -- alembic/versions/)"
|
files="$(git diff --name-only "$prev" "$new" -- alembic/versions/)"
|
||||||
@@ -196,23 +199,102 @@ detect_destructive() {
|
|||||||
echo "否"
|
echo "否"
|
||||||
}
|
}
|
||||||
DESTRUCTIVE="$(detect_destructive "$PREV_SHA" "$(git rev-parse HEAD)")"
|
DESTRUCTIVE="$(detect_destructive "$PREV_SHA" "$(git rev-parse HEAD)")"
|
||||||
info "本次迁移 destructive: $DESTRUCTIVE"
|
if [ "$DESTRUCTIVE" = "是" ]; then
|
||||||
|
warn "本次迁移含破坏性操作——回滚时将用 predeploy 快照恢复数据(见 docs/16 §3)"
|
||||||
|
else
|
||||||
|
info "本次迁移 non-destructive(回滚仅切镜像,无需数据回退)"
|
||||||
|
fi
|
||||||
|
stage_done
|
||||||
|
|
||||||
# ── 11. build + 双 tag(build 前 export,compose build 直接打 ${BACKEND_TAG} 标签)──
|
# ═══ [5/11] 部署确认 ═══
|
||||||
|
stage "部署确认"
|
||||||
|
SNAPSHOT="$BACKUP_DIR/predeploy_$(date +%Y%m%d_%H%M%S).dump"
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}┌────────────────── 部署预览 ──────────────────┐${NC}"
|
||||||
|
echo -e "${BLUE}│${NC} 当前生产版本 : ${PREV_SHA:0:12}"
|
||||||
|
echo -e "${BLUE}│${NC} 目标版本 : $NEW_SHA"
|
||||||
|
if [ "$DESTRUCTIVE" = "是" ]; then
|
||||||
|
echo -e "${BLUE}│${NC} 迁移破坏性 : ${RED}是${NC}(回滚走快照恢复)"
|
||||||
|
else
|
||||||
|
echo -e "${BLUE}│${NC} 迁移破坏性 : ${GREEN}否${NC}(回滚仅切镜像)"
|
||||||
|
fi
|
||||||
|
echo -e "${BLUE}│${NC} predeploy 快照: $SNAPSHOT"
|
||||||
|
echo -e "${BLUE}│${NC} 之后动作 : alembic upgrade head → up -d --no-deps backend worker frontend"
|
||||||
|
echo -e "${BLUE}└──────────────────────────────────────────────┘${NC}"
|
||||||
|
echo
|
||||||
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
|
info "dry-run 模式,跳过确认"
|
||||||
|
elif [ "$YES" -eq 1 ]; then
|
||||||
|
info "已用 --yes 跳过确认,直接开始"
|
||||||
|
elif [ ! -t 0 ]; then
|
||||||
|
info "非交互终端(stdin 非 TTY),自动继续"
|
||||||
|
else
|
||||||
|
if read -r -p "确认开始部署?(y/N) " yn; then
|
||||||
|
[ "$yn" = "y" ] || [ "$yn" = "Y" ] || error "已取消,未做任何改动。"
|
||||||
|
else
|
||||||
|
error "已取消(输入中断)。"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
stage_done
|
||||||
|
|
||||||
|
# ═══ [6/11] 迁移前快照 ═══
|
||||||
|
stage "迁移前快照 + 清理"
|
||||||
|
get_db_rev() {
|
||||||
|
if [ "$DRY_RUN" -eq 1 ]; then echo "-"; return 0; fi
|
||||||
|
"${COMPOSE[@]}" exec -T postgres psql -U scilit -d scilit -tAc \
|
||||||
|
"SELECT version_num FROM alembic_version" 2>/dev/null | tr -d '[:space:]' | cut -c1-12
|
||||||
|
}
|
||||||
|
# before alembic head(空则记 '-',首次部署容错)
|
||||||
|
ALEMBIC_BEFORE="$(get_db_rev || true)"
|
||||||
|
info "迁移前 alembic head: ${ALEMBIC_BEFORE:-(空/首次)}"
|
||||||
|
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
info "predeploy 全量快照(pg_dump -Fc;库大时 1–3 分钟,期间无输出属正常)"
|
||||||
|
ticker_start "predeploy 快照生成中"
|
||||||
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
|
echo -e "${YELLOW}[DRY-RUN]${NC} pg_dump → $SNAPSHOT"
|
||||||
|
else
|
||||||
|
PGPASSWORD="${PG_PASSWORD}" "${COMPOSE[@]}" exec -T postgres \
|
||||||
|
pg_dump -Fc --no-owner --no-privileges -U scilit -d scilit > "$SNAPSHOT"
|
||||||
|
if [ ! -s "$SNAPSHOT" ]; then
|
||||||
|
rm -f "$SNAPSHOT"
|
||||||
|
error "predeploy 快照生成失败(空文件),中止部署。"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
ticker_stop
|
||||||
|
info "predeploy 快照: $SNAPSHOT"
|
||||||
|
|
||||||
|
# predeploy 快照保留最近 N=3(E1:count 判定 + sort | head -n -3 | xargs -r rm)
|
||||||
|
prune_snapshots() {
|
||||||
|
if [ "$DRY_RUN" -eq 1 ]; then return 0; fi
|
||||||
|
local count
|
||||||
|
count="$(ls "$BACKUP_DIR"/predeploy_*.dump 2>/dev/null | wc -l)"
|
||||||
|
if [ "$count" -gt 3 ]; then
|
||||||
|
ls "$BACKUP_DIR"/predeploy_*.dump | sort | head -n -3 | xargs -r rm -f
|
||||||
|
info "已清理旧 predeploy 快照(保留最近 3 个)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
prune_snapshots
|
||||||
|
stage_done
|
||||||
|
|
||||||
|
# ═══ [7/11] 构建镜像 + 双 tag ═══
|
||||||
|
stage "构建镜像(backend/frontend → $NEW_SHA)"
|
||||||
if [ "$PREV_SHA" = "$(git rev-parse HEAD)" ] && [ -z "$(git diff --name-only "$PREV_SHA" -- docker-compose.prod.yml deploy)" ]; then
|
if [ "$PREV_SHA" = "$(git rev-parse HEAD)" ] && [ -z "$(git diff --name-only "$PREV_SHA" -- docker-compose.prod.yml deploy)" ]; then
|
||||||
warn "代码无更新(HEAD 未变),执行幂等重部署"
|
warn "代码无更新(HEAD 未变),执行幂等重部署"
|
||||||
fi
|
fi
|
||||||
export BACKEND_TAG="$NEW_SHA" FRONTEND_TAG="$NEW_SHA"
|
export BACKEND_TAG="$NEW_SHA" FRONTEND_TAG="$NEW_SHA"
|
||||||
info "构建并打标签: backend/frontend @ $NEW_SHA"
|
info "build 前 export 双 tag,compose build 直接打到 scilit/backend:${NEW_SHA} / scilit/frontend:${NEW_SHA}"
|
||||||
run "${COMPOSE[@]}" build backend frontend
|
run "${COMPOSE[@]}" build backend frontend
|
||||||
|
stage_done
|
||||||
|
|
||||||
# ── 12. 写 .pending-deploy(N1:快照后、迁移前;失败自动回滚唯一数据源)──
|
# ═══ [8/11] 数据库迁移 ═══
|
||||||
|
stage "数据库迁移(heads 预检 + alembic upgrade head)"
|
||||||
|
# 写 .pending-deploy(N1:快照后、迁移前;失败自动回滚唯一数据源)
|
||||||
if [ "$DRY_RUN" -ne 1 ]; then
|
if [ "$DRY_RUN" -ne 1 ]; then
|
||||||
echo "$NEW_SHA $NEW_SHA $DESTRUCTIVE $SNAPSHOT" > "$DEPLOY_DIR/.pending-deploy"
|
echo "$NEW_SHA $NEW_SHA $DESTRUCTIVE $SNAPSHOT" > "$DEPLOY_DIR/.pending-deploy"
|
||||||
fi
|
fi
|
||||||
info "写入部署标记: $DEPLOY_DIR/.pending-deploy"
|
info "写入部署标记: $DEPLOY_DIR/.pending-deploy"
|
||||||
|
|
||||||
# ── 13. alembic 多 head 预检(P15)+ 迁移(K:run --no-deps --rm,退出码即真值)──
|
|
||||||
check_single_head() {
|
check_single_head() {
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then return 0; fi
|
if [ "$DRY_RUN" -eq 1 ]; then return 0; fi
|
||||||
local n
|
local n
|
||||||
@@ -227,28 +309,35 @@ auto_rollback() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
info "执行迁移: alembic upgrade head(后台 frontend/backend 暂不切换)"
|
info "执行迁移: alembic upgrade head(已到 head 时无输出属正常)"
|
||||||
|
ticker_start "数据库迁移中"
|
||||||
if ! run "${COMPOSE[@]}" run --no-deps --rm backend alembic -c alembic/alembic.ini upgrade head; then
|
if ! run "${COMPOSE[@]}" run --no-deps --rm backend alembic -c alembic/alembic.ini upgrade head; then
|
||||||
|
ticker_stop
|
||||||
auto_rollback
|
auto_rollback
|
||||||
fi
|
fi
|
||||||
|
ticker_stop
|
||||||
|
|
||||||
# ── 14. after alembic head ──
|
|
||||||
ALEMBIC_AFTER="$(get_db_rev || true)"
|
ALEMBIC_AFTER="$(get_db_rev || true)"
|
||||||
info "迁移后 alembic head: ${ALEMBIC_AFTER:-(空/首次)}"
|
info "迁移后 alembic head: ${ALEMBIC_AFTER:-(空/首次)}"
|
||||||
|
stage_done
|
||||||
|
|
||||||
# ── 15. up -d --no-deps(显式指定应用容器,不动基础设施)──
|
# ═══ [9/11] 切换容器 + 健康轮询 ═══
|
||||||
|
stage "切换容器(up -d --no-deps backend worker frontend)+ 健康轮询"
|
||||||
info "切换容器到新版本(backend worker frontend)"
|
info "切换容器到新版本(backend worker frontend)"
|
||||||
|
ticker_start "切换容器中"
|
||||||
if ! run "${COMPOSE[@]}" up -d --no-deps backend worker frontend; then
|
if ! run "${COMPOSE[@]}" up -d --no-deps backend worker frontend; then
|
||||||
|
ticker_stop
|
||||||
auto_rollback
|
auto_rollback
|
||||||
fi
|
fi
|
||||||
|
ticker_stop
|
||||||
|
|
||||||
# ── 16. 健康轮询(V6:up 后仍在 start_period,轮询而非单次 curl)──
|
|
||||||
wait_backend_healthy() {
|
wait_backend_healthy() {
|
||||||
local i
|
local i
|
||||||
for i in $(seq 1 "$HEALTH_POLLS"); do
|
for i in $(seq 1 "$HEALTH_POLLS"); do
|
||||||
if "${COMPOSE[@]}" exec -T backend curl -sf http://localhost:8000/health >/dev/null 2>&1; then
|
if "${COMPOSE[@]}" exec -T backend curl -sf http://localhost:8000/health >/dev/null 2>&1; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
echo -e "${YELLOW}[$i/$HEALTH_POLLS] backend 尚未就绪,5s 后重试…${NC}"
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
return 1
|
return 1
|
||||||
@@ -269,8 +358,10 @@ elif wait_backend_healthy; then
|
|||||||
else
|
else
|
||||||
auto_rollback
|
auto_rollback
|
||||||
fi
|
fi
|
||||||
|
stage_done
|
||||||
|
|
||||||
# ── 17. 成功 → 写 versions.log + 删 .pending-deploy ──
|
# ═══ [10/11] 记录版本 ═══
|
||||||
|
stage "记录版本(versions.log + 清理部署标记)"
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
echo -e "${YELLOW}[DRY-RUN]${NC} 追加 versions.log + 删除 .pending-deploy"
|
echo -e "${YELLOW}[DRY-RUN]${NC} 追加 versions.log + 删除 .pending-deploy"
|
||||||
else
|
else
|
||||||
@@ -281,8 +372,10 @@ else
|
|||||||
rm -f "$DEPLOY_DIR/.pending-deploy"
|
rm -f "$DEPLOY_DIR/.pending-deploy"
|
||||||
fi
|
fi
|
||||||
info "部署成功,已写入 versions.log"
|
info "部署成功,已写入 versions.log"
|
||||||
|
stage_done
|
||||||
|
|
||||||
# ── 18. 镜像治理(只按 count 清理带 tag 版本镜像;age-prune 只清 dangling)──
|
# ═══ [11/11] 镜像治理 ═══
|
||||||
|
stage "镜像治理(保留最近 $KEEP_TAGS 个带 tag 版本 + 清理 dangling)"
|
||||||
prune_images() {
|
prune_images() {
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then return 0; fi
|
if [ "$DRY_RUN" -eq 1 ]; then return 0; fi
|
||||||
local repo count to_remove t
|
local repo count to_remove t
|
||||||
@@ -300,5 +393,6 @@ prune_images() {
|
|||||||
docker image prune --filter "until=168h" -f >/dev/null 2>&1 || true
|
docker image prune --filter "until=168h" -f >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
prune_images
|
prune_images
|
||||||
|
stage_done
|
||||||
|
|
||||||
info "全部完成 ✅ 当前版本: $NEW_SHA"
|
info "全部完成 ✅ 当前版本: $NEW_SHA"
|
||||||
|
|||||||
Reference in New Issue
Block a user