#!/bin/bash # === 配置项 === SERVER="dxc@172.16.0.198" REMOTE_DIR="/opt/inventory-app" TIMESTAMP=$(date +%Y%m%d_%H%M) REMOTE_BACKUP_BASE="$REMOTE_DIR/data_copy" REMOTE_BACKUP_DIR="$REMOTE_BACKUP_BASE/$TIMESTAMP" echo "===================================================" echo "🚀 开始自动化增量部署 (含自我修复与依赖瘦身)" echo "===================================================" # 1. 远端备份与环境急救 echo "[1/4] 正在服务器上急救环境并备份旧代码 (可能需要输入密码)..." ssh -t $SERVER "sudo mkdir -p $REMOTE_BACKUP_DIR && \ cd $REMOTE_DIR && \ echo '>> 检查并修复缺失目录 (防止 tar 崩溃)...' && \ sudo mkdir -p inventory-backend inventory-web models_prod && \ echo '>> 执行代码备份...' && \ sudo tar -czf $REMOTE_BACKUP_DIR/code_backup.tar.gz inventory-backend inventory-web docker-compose.prod.yml && \ echo '>> 备份 SSL 证书...' && \ (sudo tar -czf $REMOTE_BACKUP_DIR/ssl_backup.tar.gz ssl 2>/dev/null || echo '⚠️ ssl 目录不存在,跳过') && \ echo '>> 检查 CLIP 模型文件...' && \ if [ ! -f models_prod/clip_vision.onnx ]; then \ echo '⚠️ 警告: models_prod/clip_vision.onnx 不存在!拍照识图功能将无法使用。'; \ echo '⚠️ 请手动上传: scp inventory-backend/models/clip_vision.onnx ${SERVER}:${REMOTE_DIR}/models_prod/'; \ else \ echo '✅ CLIP 模型文件已存在'; \ fi && \ echo '>> 执行清理:仅保留 data_copy 下最新的 2 个备份...' && \ cd $REMOTE_BACKUP_BASE && \ sudo sh -c 'ls -dt */ 2>/dev/null | tail -n +3 | xargs -I {} rm -rf {} || true'" if [ $? -ne 0 ]; then echo "❌ 服务器备份或清理失败,终止部署!"; exit 1; fi # 2. 本地精准打包 (绝对屏蔽垃圾文件) echo "[2/4] 正在本地打包新代码 (执行终极瘦身)..." tar -czf deploy.tar.gz \ --exclude="*/node_modules" \ --exclude="*/venv" \ --exclude="*/.venv" \ --exclude="*/__pycache__" \ --exclude="*.pyc" \ --exclude="*/.git" \ --exclude="*/.idea" \ --exclude="*/.vscode" \ --exclude="inventory-backend/uploads" \ --exclude="inventory-backend/models" \ --exclude="inventory-backend/pgdata" \ --exclude="inventory-backend/pgdata_docker" \ --exclude="inventory-backend/test_*.py" \ --exclude="inventory-backend/fix_*.sql" \ --exclude="inventory-backend/*.sql" \ --exclude="inventory-backend/import_test_edge_cases.xlsx" \ --exclude="inventory-backend/import_edge_case_test.xlsx" \ --exclude="inventory-web/tsc-out.txt" \ --exclude="deploy.tar.gz" \ --exclude="data_copy" \ inventory-backend inventory-web docker-compose.prod.yml # 检查压缩包大小是否正常 (如果超过 50MB 则发出警告) FILESIZE=$(stat -c%s "deploy.tar.gz" 2>/dev/null || stat -f%z "deploy.tar.gz") MB_SIZE=$((FILESIZE / 1024 / 1024)) echo ">> 打包完成,当前传输包大小: ${MB_SIZE} MB" if [ "$MB_SIZE" -gt 50 ]; then echo "⚠️ 警告:包体积仍大于 50MB,可能存在未排除的大文件,但继续执行..." fi # 3. 传输到生产环境的 /tmp 目录 echo "[3/4] 正在传输代码到服务器的临时目录..." scp deploy.tar.gz $SERVER:/tmp/deploy.tar.gz if [ $? -ne 0 ]; then echo "❌ 传输失败!可能是网络中断,终止部署!" rm -f deploy.tar.gz exit 1 fi # 4. 服务器执行替换与重启 echo "[4/4] 正在生产环境执行热更新 (可能需要输入密码)..." ssh -t $SERVER "cd $REMOTE_DIR && \ echo '>> 移除历史旧目录...' && \ sudo rm -rf inventory-backend_old inventory-web_old && \ echo '>> 备份当前目录为 old...' && \ (sudo mv inventory-backend inventory-backend_old 2>/dev/null || true) && \ (sudo mv inventory-web inventory-web_old 2>/dev/null || true) && \ echo '>> 部署新代码...' && \ sudo mv /tmp/deploy.tar.gz . && \ sudo tar -xzf deploy.tar.gz && \ echo '>> 重启 Docker 容器...' && \ sudo docker compose -f docker-compose.prod.yml up -d --build backend frontend && \ echo '>> 验证 nginx 代理配置...' && \ sleep 5 && \ if sudo docker exec inventory_ui_prod grep -q "inventory_api_prod" /etc/nginx/conf.d/default.conf; then \ echo '✅ nginx 代理已指向 inventory_api_prod'; \ else \ echo '⚠️ 警告: nginx 代理仍是 backend,可能出现 /api/ 404!请检查 nginx.conf'; \ fi && \ echo '>> 验证 API 连通性...' && \ API_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" "https://localhost/api/inbound/base/units") && \ if [ "$API_CODE" = "404" ]; then \ echo '⚠️ 警告: API 返回 404,代理可能指向了错误的容器!'; \ else \ echo "✅ API 连通性正常 HTTP=$API_CODE"; \ fi && \ sudo rm deploy.tar.gz" if [ $? -ne 0 ]; then echo "❌ 远端启动失败,请检查 Docker 日志!" else echo "===================================================" echo "✅ 部署完美完成!请刷新网页查看效果。" echo "===================================================" fi # 扫尾清理本地文件 rm -f deploy.tar.gz