#!/usr/bin/env bash # 代码/镜像更新(服务器上不改库)— 任一步失败立即中断 # 用法: # 1) 本机 DataGrip 已执行 deploy/datagrip-manual-migrate-20260716.sql # 2) 服务器:bash deploy/update-code-only-bt-host-mysql.sh # # 特点:串行 build;跳过 migrate/seed;失败 fail-fast,不继续后续步骤 set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.bt-host-mysql.yml" ENV_FILE="${ROOT_DIR}/.env" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log() { echo -e "${GREEN}[update-code]${NC} $*"; } warn() { echo -e "${YELLOW}[update-code]${NC} $*"; } err() { echo -e "${RED}[update-code]${NC} $*" >&2; } CURRENT_STEP="init" on_err() { local code=$? err "失败中断:步骤「${CURRENT_STEP}」退出码=${code}" err "已停止继续执行(不会启动半成品镜像)。请根据上方日志修复后重跑本脚本。" err "若容器已 stop:修复后重新执行本脚本即可恢复。" exit "$code" } trap on_err ERR require_file() { local path="$1" if [[ ! -f "$path" ]]; then err "缺少文件: $path" exit 1 fi } require_env_key() { local key="$1" if ! grep -qE "^[[:space:]]*${key}=" "$ENV_FILE"; then err ".env 缺少 ${key}(新功能不会生效)" exit 1 fi } run_step() { local name="$1" shift CURRENT_STEP="$name" log "→ ${name}" "$@" log "✓ ${name} 完成" } # ---------- 前置校验(失败立即退出)---------- CURRENT_STEP="preflight" log "前置校验..." if [[ ! -f "$ENV_FILE" ]]; then err "缺少 .env,禁止继续(避免误用模板覆盖生产配置)" exit 1 fi if [[ ! -f "$COMPOSE_FILE" ]]; then err "缺少 compose 文件: $COMPOSE_FILE" exit 1 fi if ! command -v docker >/dev/null 2>&1; then err "未安装 docker" exit 1 fi if ! docker compose version >/dev/null 2>&1; then err "需要 Docker Compose V2" exit 1 fi require_file "${ROOT_DIR}/app/api/flock/quotes/route.ts" require_file "${ROOT_DIR}/workers/rpa/flock-freight.ts" require_file "${ROOT_DIR}/workers/rpa/mothership-logged-in-quote.ts" require_file "${ROOT_DIR}/Dockerfile" require_file "${ROOT_DIR}/Dockerfile.worker" require_file "${ROOT_DIR}/package.json" require_file "${ROOT_DIR}/package-lock.json" require_env_key "FLOCK_RPA_ENABLED" require_env_key "NEXT_PUBLIC_FLOCK_POLL_TIMEOUT_MS" require_env_key "DATABASE_URL" require_env_key "JWT_SECRET" if grep -qE "^[[:space:]]*RPA_BROWSER_CHANNEL=chrome" "$ENV_FILE"; then err ".env 存在 RPA_BROWSER_CHANNEL=chrome(Docker 内会启动失败),请删除该行后重跑" exit 1 fi # 可用内存过低时警告(不强制中断,避免误杀) if command -v free >/dev/null 2>&1; then avail_kb="$(free -k | awk '/^Mem:/{print $7}')" if [[ -n "${avail_kb:-}" && "$avail_kb" -lt 400000 ]]; then warn "可用内存偏低(约 $((avail_kb / 1024))MB),构建可能 OOM;建议先 stop 其他重负载或加 Swap" fi fi log "前置校验通过" cd "$ROOT_DIR" compose=(docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE") export DOCKER_BUILDKIT=1 export BUILDKIT_MAX_PARALLELISM=1 # ---------- 串行更新(任一步失败由 trap 中断)---------- run_step "停止 next-app/rpa-worker/scheduler" \ "${compose[@]}" stop next-app rpa-worker scheduler run_step "构建 next-app" \ "${compose[@]}" build next-app run_step "构建 rpa-worker" \ "${compose[@]}" build rpa-worker run_step "构建 scheduler" \ "${compose[@]}" build scheduler run_step "启动全部服务" \ "${compose[@]}" up -d CURRENT_STEP="校验容器状态" log "→ 校验容器状态" "${compose[@]}" ps # 关键服务必须处于 running for svc in next-app rpa-worker scheduler redis; do state="$("${compose[@]}" ps --status running --services 2>/dev/null | grep -x "$svc" || true)" if [[ -z "$state" ]]; then err "服务未处于 running: ${svc}" err "查看日志: ${compose[*]} logs --tail 80 ${svc}" exit 1 fi log "✓ ${svc} running" done CURRENT_STEP="done" trap - ERR warn "库结构须已在 DataGrip 用 SQL 改完(本脚本不碰库)" warn "以后不要再跑 install-bt-host-mysql.sh(会自动 migrate)" warn "建议清报价缓存:" echo " ${compose[*]} exec redis redis-cli FLUSHDB" log "完成(代码-only 更新,全程失败即中断)"