286 lines
13 KiB
Bash
286 lines
13 KiB
Bash
#!/usr/bin/env bash
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 蝶雅智能体平台 — 打包部署脚本
|
||
#
|
||
# 默认:部署 代码+知识库+数据库结构(不覆盖数据)
|
||
# 加 --db 参数:同时全量覆盖数据库(含数据)
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
set -euo pipefail
|
||
|
||
# ═══ 配置区 ═══
|
||
REMOTE_IP="192.168.11.79"
|
||
REMOTE_USER="dieya"
|
||
REMOTE_DIR="/home/dieya/.openclaw/workspace/dy-dashboard"
|
||
LOCAL_DIR="/home/gxd/.openclaw/workspace/dy-dashboard"
|
||
VAULT_DIR="/home/gxd/蝶雅供应链/知识库"
|
||
PROD_VAULT_DIR="${REMOTE_DIR}/vault"
|
||
|
||
DB_USER="dy_app"
|
||
DB_PASS="dy_data_2026"
|
||
DB_NAME="dy_data"
|
||
|
||
# ═══ 解析参数 ═══
|
||
DEPLOY_DB=false
|
||
for arg in "$@"; do
|
||
[ "$arg" = "--db" ] && DEPLOY_DB=true
|
||
done
|
||
|
||
# 时间戳
|
||
TS=$(date +%Y%m%d_%H%M%S)
|
||
STAGE_DIR="/tmp/dy-pack-${TS}"
|
||
PACKAGE="/tmp/dy-dashboard-full-${TS}.tar.gz"
|
||
DUMP_FILE="${STAGE_DIR}/dy_data_full.sql"
|
||
SCHEMA_FILE="${STAGE_DIR}/dy_data_schema.sql"
|
||
|
||
# ═══ 颜色 ═══
|
||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 本地打包
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
echo ""
|
||
echo -e "${GREEN}═══════════════════════════════════════════════${NC}"
|
||
echo -e "${GREEN} 蝶雅智能体平台 — 打包部署${NC}"
|
||
echo -e "${GREEN}═══════════════════════════════════════════════${NC}"
|
||
echo ""
|
||
info "时间:${TS}"
|
||
$DEPLOY_DB && info "模式:全量(代码+知识库+数据库含数据)⚠️" || info "模式:增量(代码+知识库+数据库结构,保留数据)"
|
||
|
||
# 1.1 导出数据库
|
||
mkdir -p "${STAGE_DIR}/vault/知识库"
|
||
|
||
# 导出数据库结构(始终导出,用于更新结构不覆盖数据)
|
||
info "导出数据库结构..."
|
||
mysqldump -u"${DB_USER}" -p"${DB_PASS}" --no-data --skip-add-drop-table \
|
||
--routines --triggers --events \
|
||
--no-tablespaces \
|
||
--ignore-table="${DB_NAME}.feedback" \
|
||
"${DB_NAME}" > "${SCHEMA_FILE}"
|
||
SCHEMA_SIZE=$(du -h "${SCHEMA_FILE}" | cut -f1)
|
||
ok "结构已导出(${SCHEMA_SIZE})"
|
||
|
||
# 导出完整数据库(仅 --db 时使用)
|
||
if $DEPLOY_DB; then
|
||
info "导出完整数据库..."
|
||
mysqldump -u"${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" \
|
||
--routines --triggers --events --single-transaction \
|
||
--complete-insert --hex-blob --no-tablespaces \
|
||
--ignore-table="${DB_NAME}.feedback" \
|
||
> "${DUMP_FILE}"
|
||
SQL_SIZE=$(du -h "${DUMP_FILE}" | cut -f1)
|
||
ok "完整数据库已导出(${SQL_SIZE})"
|
||
fi
|
||
|
||
# 1.2 复制项目源码
|
||
info "复制项目文件..."
|
||
rsync -a --delete \
|
||
--exclude='venv' \
|
||
--exclude='__pycache__' \
|
||
--exclude='*.pyc' \
|
||
--exclude='backups' \
|
||
--exclude='logs' \
|
||
--exclude='.git' \
|
||
--exclude='*.tar.gz' \
|
||
--exclude='*.sql' \
|
||
--exclude='生产环境部署指南.md' \
|
||
--exclude='static/uploads/feedback' \
|
||
--exclude='static/uploads/new_products' \
|
||
--exclude='node_modules' \
|
||
--exclude='pack_deploy_full.sh' \
|
||
"${LOCAL_DIR}/" "${STAGE_DIR}/"
|
||
|
||
# 1.3 复制知识库
|
||
info "复制知识库文件..."
|
||
mkdir -p "${STAGE_DIR}/vault/知识库"
|
||
cp -r "${VAULT_DIR}/." "${STAGE_DIR}/vault/知识库/"
|
||
ok "项目与知识库已准备"
|
||
|
||
# 1.4 打包
|
||
info "打包..."
|
||
cd "${STAGE_DIR}"
|
||
tar -czf "${PACKAGE}" .
|
||
PACKAGE_SIZE=$(du -h "${PACKAGE}" | cut -f1)
|
||
ok "打包完成(${PACKAGE_SIZE}):${PACKAGE}"
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 上传到生产
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
echo ""
|
||
info "上传..."
|
||
ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \
|
||
"${REMOTE_USER}@${REMOTE_IP}" "echo SSH_OK" 2>/dev/null | grep -q "SSH_OK" || \
|
||
err "SSH 连接失败"
|
||
|
||
scp "${PACKAGE}" "${REMOTE_USER}@${REMOTE_IP}:/tmp/"
|
||
ok "上传完成"
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 生产环境部署
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
info "生产环境部署..."
|
||
ssh "${REMOTE_USER}@${REMOTE_IP}" "
|
||
set -e
|
||
|
||
echo ' → 停服务...'
|
||
PID=\$(lsof -t -i:18888 2>/dev/null || true)
|
||
[ -n \"\$PID\" ] && kill -9 \$PID 2>/dev/null || true
|
||
sleep 1
|
||
|
||
TIMESTAMP=\$(date +%Y%m%d_%H%M%S)
|
||
echo ' → 独立备份新品图片目录...'
|
||
mkdir -p ${REMOTE_DIR}_backups
|
||
if [ -d \"${REMOTE_DIR}/static/uploads/new_products\" ]; then
|
||
tar -czf \"${REMOTE_DIR}_backups/new_products_pre-${TS}-\${TIMESTAMP}.tar.gz\" \\
|
||
-C \"${REMOTE_DIR}/static/uploads\" new_products
|
||
echo \" 新品图片独立备份: new_products_pre-${TS}-\${TIMESTAMP}.tar.gz\"
|
||
fi
|
||
|
||
echo ' → 备份当前项目(包含 feedback 图片)...'
|
||
mkdir -p ${REMOTE_DIR}_backups
|
||
if [ -d \"${REMOTE_DIR}\" ] && [ -n \"\$(ls -A ${REMOTE_DIR} 2>/dev/null)\" ]; then
|
||
tar -czf \"${REMOTE_DIR}_backups/pre-${TS}-\${TIMESTAMP}.tar.gz\" -C \"${REMOTE_DIR}\" .
|
||
echo \" 备份: pre-${TS}-\${TIMESTAMP}.tar.gz\"
|
||
fi
|
||
|
||
echo ' → 保留上传图片...'
|
||
[ -d \"${REMOTE_DIR}/static/uploads/feedback\" ] && mv \"${REMOTE_DIR}/static/uploads/feedback\" /tmp/feedback-backup-${TS}
|
||
[ -d \"${REMOTE_DIR}/static/uploads/new_products\" ] && mv \"${REMOTE_DIR}/static/uploads/new_products\" /tmp/new-products-backup-${TS}
|
||
|
||
rm -rf \"${REMOTE_DIR}\"
|
||
mkdir -p \"${REMOTE_DIR}\"
|
||
|
||
echo ' → 解压...'
|
||
tar -xzf /tmp/dy-dashboard-full-${TS}.tar.gz -C \"${REMOTE_DIR}\"
|
||
|
||
echo ' → 恢复上传图片...'
|
||
mkdir -p \"${REMOTE_DIR}/static/uploads\"
|
||
if [ -d /tmp/feedback-backup-${TS} ]; then
|
||
mv /tmp/feedback-backup-${TS} \"${REMOTE_DIR}/static/uploads/feedback\"
|
||
echo \" ✅ 已恢复 feedback 图片\"
|
||
fi
|
||
mkdir -p \"${REMOTE_DIR}/static/uploads/new_products\"
|
||
if [ -d /tmp/new-products-backup-${TS} ]; then
|
||
cp -a /tmp/new-products-backup-${TS}/. \"${REMOTE_DIR}/static/uploads/new_products/\"
|
||
rm -rf /tmp/new-products-backup-${TS}
|
||
echo \" ✅ 已合并恢复生产新品图片\"
|
||
fi
|
||
mkdir -p \"${REMOTE_DIR}/static/uploads/feedback\" \"${REMOTE_DIR}/static/uploads/new_products\"
|
||
if [ -f \"${REMOTE_DIR}_backups/new_products_pre-${TS}-\${TIMESTAMP}.tar.gz\" ]; then
|
||
BEFORE_IMAGES=\$(tar -tzf \"${REMOTE_DIR}_backups/new_products_pre-${TS}-\${TIMESTAMP}.tar.gz\" 2>/dev/null | grep -Ev '/$' | wc -l)
|
||
AFTER_IMAGES=\$(find \"${REMOTE_DIR}/static/uploads/new_products\" -maxdepth 1 -type f | wc -l)
|
||
[ \"\${AFTER_IMAGES}\" -ge \"\${BEFORE_IMAGES}\" ] || {
|
||
echo \" ❌ 新品图片恢复不完整:部署前 \${BEFORE_IMAGES} 个,部署后 \${AFTER_IMAGES} 个\"
|
||
exit 1
|
||
}
|
||
echo \" ✅ 新品图片保护校验通过:部署前 \${BEFORE_IMAGES} 个,部署后 \${AFTER_IMAGES} 个\"
|
||
fi
|
||
|
||
echo ' → 更新 VAULT_PATH...'
|
||
sed -i 's|^VAULT_PATH =.*|VAULT_PATH = \"${PROD_VAULT_DIR}\"|' \"${REMOTE_DIR}/config.py\"
|
||
|
||
# ── 数据库结构更新 ──
|
||
if [ -f \"${REMOTE_DIR}/dy_data_schema.sql\" ]; then
|
||
echo ' → 更新数据库结构(保留数据)...'
|
||
sed 's/CREATE TABLE IF NOT EXISTS/CREATE TABLE/g; s/CREATE TABLE/CREATE TABLE IF NOT EXISTS/g' \
|
||
\"${REMOTE_DIR}/dy_data_schema.sql\" | \
|
||
sed 's/CREATE VIEW/CREATE OR REPLACE VIEW/g' | \
|
||
mysql -u\"${DB_USER}\" -p\"${DB_PASS}\" \"${DB_NAME}\" 2>&1 | grep -v 'already exists' || true
|
||
rm -f \"${REMOTE_DIR}/dy_data_schema.sql\"
|
||
echo ' ✅ 数据库结构已更新'
|
||
fi
|
||
|
||
echo ' → 重建 venv...'
|
||
cd \"${REMOTE_DIR}\"
|
||
if [ -f requirements.txt ]; then
|
||
python3 -m venv venv
|
||
source venv/bin/activate
|
||
pip install -q -r requirements.txt
|
||
deactivate
|
||
fi
|
||
|
||
echo ' → 清理缓存...'
|
||
find \"${REMOTE_DIR}\" -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null
|
||
find \"${REMOTE_DIR}\" -name '*.pyc' -delete 2>/dev/null
|
||
|
||
echo ' → 迁移新增列...'
|
||
cat > /tmp/migrate_cols.sql <<'SQLEOF'
|
||
ALTER TABLE \`domestic_orders\` ADD COLUMN \`待提数量\` INT DEFAULT 0;
|
||
ALTER TABLE \`overseas_orders\` ADD COLUMN \`待提数量\` INT DEFAULT 0;
|
||
ALTER TABLE \`overseas_orders\` ADD COLUMN \`订单下达供应商时间\` DATETIME DEFAULT NULL;
|
||
ALTER TABLE \`packaging_orders\` ADD COLUMN \`待提数量\` INT DEFAULT 0;
|
||
ALTER TABLE \`packaging_orders\` ADD COLUMN \`实际到货数量\` INT DEFAULT 0;
|
||
ALTER TABLE \`packaging_orders\` ADD COLUMN \`未到货数\` INT DEFAULT 0;
|
||
ALTER TABLE \`ai_analysis_history\` ADD COLUMN \`content\` MEDIUMTEXT DEFAULT NULL;
|
||
SQLEOF
|
||
mysql --force -u"${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" < /tmp/migrate_cols.sql 2>&1 | grep -viE 'duplicate column|already exists' || true
|
||
rm -f /tmp/migrate_cols.sql
|
||
echo ' ✅ 数据库列迁移完成'
|
||
|
||
echo ' → 启动服务...'
|
||
mkdir -p logs
|
||
nohup venv/bin/gunicorn -w 4 -b 0.0.0.0:18888 --timeout 120 \
|
||
--access-logfile logs/access.log \
|
||
--error-logfile logs/error.log \
|
||
app:app > /dev/null 2>&1 &
|
||
|
||
echo ' → 验证...'
|
||
sleep 5
|
||
curl -s http://127.0.0.1:18888/ > /dev/null 2>&1 && echo '__DEPLOY_OK__' || echo '__DEPLOY_FAIL__'
|
||
" | grep -q "__DEPLOY_OK__" && ok "生产环境部署成功 ✅" || err "生产环境部署失败 ❌"
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 全量数据库覆盖(仅 --db 模式)
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
if $DEPLOY_DB; then
|
||
warn "准备全量恢复数据库(将覆盖生产数据)..."
|
||
scp "${DUMP_FILE}" "${REMOTE_USER}@${REMOTE_IP}:/tmp/"
|
||
ssh "${REMOTE_USER}@${REMOTE_IP}" "
|
||
set -e
|
||
kill -9 \$(lsof -t -i:18888 2>/dev/null) 2>/dev/null || true
|
||
sleep 1
|
||
echo ' → 恢复数据库...'
|
||
mysql -u\"${DB_USER}\" -p\"${DB_PASS}\" \"${DB_NAME}\" < /tmp/dy_data_full.sql
|
||
echo ' → 启动服务...'
|
||
cd ${REMOTE_DIR}
|
||
nohup venv/bin/gunicorn -w 4 -b 0.0.0.0:18888 --timeout 120 \
|
||
--access-logfile logs/access.log \
|
||
--error-logfile logs/error.log \
|
||
app:app > /dev/null 2>&1 &
|
||
sleep 5
|
||
curl -s http://127.0.0.1:18888/ > /dev/null 2>&1 && echo '__DB_OK__' || echo '__DB_FAIL__'
|
||
" | grep -q "__DB_OK__" && ok "数据库全量恢复完成 ✅" || err "数据库全量恢复失败 ❌"
|
||
fi
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 收尾
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
info "清理本地临时文件..."
|
||
rm -rf "${STAGE_DIR}" "${PACKAGE}"
|
||
ok "清理完成"
|
||
|
||
echo ""
|
||
echo -e "${GREEN}═══════════════════════════════════════════════${NC}"
|
||
echo -e "${GREEN} ✅ 部署完成!${NC}"
|
||
echo -e "${GREEN}═══════════════════════════════════════════════${NC}"
|
||
echo ""
|
||
echo -e " 生产地址:${CYAN}http://${REMOTE_IP}:18888${NC}"
|
||
echo -e " 包大小:${PACKAGE_SIZE}"
|
||
echo ""
|
||
if $DEPLOY_DB; then
|
||
echo -e " ${RED}⚠️ 全量模式:数据库结构和数据均已覆盖${NC}"
|
||
else
|
||
echo -e " ${GREEN}✅ 增量模式:代码 + 知识库 + 结构已更新,数据未覆盖${NC}"
|
||
echo -e " ${YELLOW}💡 如需全量覆盖数据库,运行: bash pack_deploy_full.sh --db${NC}"
|
||
fi
|
||
echo ""
|