#!/bin/bash ############################################################################### # 本地部署脚本 # 在本地运行,通过 SSH 连接服务器并执行部署 ############################################################################### set -e # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # 获取脚本所在目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" ############################################################################### # 函数:打印带颜色的消息 ############################################################################### log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[✓]${NC} $1"; } log_warning() { echo -e "${YELLOW}[⚠]${NC} $1"; } log_error() { echo -e "${RED}[✗]${NC} $1"; } log_step() { echo -e "${CYAN}${BOLD}[$1]${NC} $2"; } ############################################################################### # 函数:加载配置文件 ############################################################################### load_config() { if [ ! -f "$PROJECT_ROOT/.env.deploy" ]; then log_error "配置文件不存在: $PROJECT_ROOT/.env.deploy" echo "" echo "请先运行以下命令进行配置:" echo " npm run deploy:setup" echo "" echo "或者手动创建配置文件:" echo " cp .env.deploy.example .env.deploy" echo "" exit 1 fi # 加载配置 source "$PROJECT_ROOT/.env.deploy" # 检查必需的配置项 if [ -z "$SERVER_HOST" ] || [ -z "$SERVER_USER" ]; then log_error "配置不完整,请检查 .env.deploy 文件" echo "必需配置项:" echo " - SERVER_HOST: 服务器地址" echo " - SERVER_USER: SSH 用户名" exit 1 fi log_success "配置加载完成" } ############################################################################### # 函数:检查本地 Git 状态 ############################################################################### check_local_git() { log_step "1/8" "检查本地代码" cd "$PROJECT_ROOT" # 检查是否是 Git 仓库 if [ ! -d ".git" ]; then log_error "当前目录不是 Git 仓库" exit 1 fi # 获取当前分支 local current_branch=$(git branch --show-current) log_info "当前分支: $current_branch" # 检查是否有未提交的更改 if ! git diff-index --quiet HEAD --; then log_warning "存在未提交的更改" echo "" git status --short echo "" read -p "是否继续部署? (y/n): " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then log_info "部署已取消" exit 0 fi fi # 获取最新提交信息 COMMIT_HASH=$(git rev-parse --short HEAD) COMMIT_MESSAGE=$(git log -1 --pretty=%B | head -n 1) COMMIT_AUTHOR=$(git log -1 --pretty=%an) log_info "最新提交: $COMMIT_HASH - $COMMIT_MESSAGE" log_info "提交作者: $COMMIT_AUTHOR" log_success "本地代码检查完成" echo "" } ############################################################################### # 函数:显示部署预览 ############################################################################### show_deploy_preview() { log_step "2/8" "部署预览" echo "" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ 部署预览 ║" echo "╚════════════════════════════════════════════════════════════════╝" echo "" echo -e "${BOLD}项目信息:${NC}" echo " 项目名称: vf_react" echo " 部署环境: 生产环境" echo " 目标服务器: $SERVER_USER@$SERVER_HOST" echo "" echo -e "${BOLD}代码信息:${NC}" echo " 当前分支: $(git branch --show-current)" echo " 提交版本: $COMMIT_HASH" echo " 提交信息: $COMMIT_MESSAGE" echo " 提交作者: $COMMIT_AUTHOR" echo "" echo -e "${BOLD}部署路径:${NC}" echo " Git 仓库: $REMOTE_PROJECT_PATH" echo " 生产目录: $PRODUCTION_PATH" echo "" echo "════════════════════════════════════════════════════════════════" echo "" # 询问是否继续 read -p "确认部署到生产环境? (yes/no): " -r echo "" if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then log_info "部署已取消" exit 0 fi } ############################################################################### # 函数:测试 SSH 连接 ############################################################################### test_ssh_connection() { log_step "3/8" "测试 SSH 连接" local ssh_options="-o ConnectTimeout=${SSH_TIMEOUT:-30} -o BatchMode=yes" if [ -n "$SSH_KEY_PATH" ]; then ssh_options="$ssh_options -i $SSH_KEY_PATH" fi if [ -n "$SERVER_PORT" ]; then ssh_options="$ssh_options -p $SERVER_PORT" fi # 测试连接 if ssh $ssh_options "$SERVER_USER@$SERVER_HOST" "echo 'SSH 连接成功'" > /dev/null 2>&1; then log_success "SSH 连接成功" else log_error "SSH 连接失败" echo "" echo "请检查:" echo " 1. 服务器地址是否正确: $SERVER_HOST" echo " 2. SSH 用户名是否正确: $SERVER_USER" echo " 3. SSH 密钥是否配置正确" echo " 4. 服务器端口是否正确: ${SERVER_PORT:-22}" exit 1 fi echo "" } ############################################################################### # 函数:上传服务器端脚本 ############################################################################### upload_server_scripts() { log_step "4/8" "上传部署脚本" local ssh_options="" if [ -n "$SSH_KEY_PATH" ]; then ssh_options="-i $SSH_KEY_PATH" fi if [ -n "$SERVER_PORT" ]; then ssh_options="$ssh_options -P $SERVER_PORT" fi # 创建远程脚本目录 ssh $ssh_options "$SERVER_USER@$SERVER_HOST" "mkdir -p /tmp/deploy-scripts" || { log_error "创建远程目录失败" exit 1 } # 上传脚本 scp $ssh_options \ "$SCRIPT_DIR/deploy-on-server.sh" \ "$SCRIPT_DIR/rollback-on-server.sh" \ "$SCRIPT_DIR/notify-wechat.sh" \ "$SERVER_USER@$SERVER_HOST":/tmp/deploy-scripts/ > /dev/null 2>&1 || { log_error "上传脚本失败" exit 1 } # 设置执行权限 ssh $ssh_options "$SERVER_USER@$SERVER_HOST" "chmod +x /tmp/deploy-scripts/*.sh" || { log_error "设置脚本权限失败" exit 1 } log_success "部署脚本上传完成" echo "" } ############################################################################### # 函数:执行服务器端部署 ############################################################################### execute_remote_deployment() { log_step "5/8" "执行远程部署" echo "" local ssh_options="" if [ -n "$SSH_KEY_PATH" ]; then ssh_options="-i $SSH_KEY_PATH" fi if [ -n "$SERVER_PORT" ]; then ssh_options="$ssh_options -p $SERVER_PORT" fi # 构建环境变量 local env_vars="REMOTE_PROJECT_PATH=$REMOTE_PROJECT_PATH " env_vars+="PRODUCTION_PATH=$PRODUCTION_PATH " env_vars+="BACKUP_DIR=$BACKUP_DIR " env_vars+="LOG_DIR=$LOG_DIR " env_vars+="DEPLOY_BRANCH=$DEPLOY_BRANCH " env_vars+="KEEP_BACKUPS=$KEEP_BACKUPS " env_vars+="RUN_NPM_INSTALL=$RUN_NPM_INSTALL" # 记录开始时间 DEPLOY_START_TIME=$(date +%s) # 执行部署脚本 ssh $ssh_options "$SERVER_USER@$SERVER_HOST" "$env_vars bash /tmp/deploy-scripts/deploy-on-server.sh" || { log_error "远程部署失败" send_failure_notification "部署脚本执行失败" exit 1 } # 记录结束时间 DEPLOY_END_TIME=$(date +%s) DEPLOY_DURATION=$((DEPLOY_END_TIME - DEPLOY_START_TIME)) echo "" log_success "远程部署完成" echo "" } ############################################################################### # 函数:发送成功通知 ############################################################################### send_success_notification() { log_step "6/8" "发送部署通知" if [ "$ENABLE_WECHAT_NOTIFY" = "true" ]; then local minutes=$((DEPLOY_DURATION / 60)) local seconds=$((DEPLOY_DURATION % 60)) local duration="${minutes}分${seconds}秒" bash "$SCRIPT_DIR/notify-wechat.sh" success \ "$DEPLOY_BRANCH" \ "$COMMIT_HASH" \ "$COMMIT_MESSAGE" \ "$duration" \ "$USER" || { log_warning "企业微信通知发送失败" } else log_info "企业微信通知未启用" fi echo "" } ############################################################################### # 函数:发送失败通知 ############################################################################### send_failure_notification() { local error_message="$1" if [ "$ENABLE_WECHAT_NOTIFY" = "true" ]; then bash "$SCRIPT_DIR/notify-wechat.sh" failure \ "$DEPLOY_BRANCH" \ "$error_message" \ "$USER" 2>/dev/null || true fi } ############################################################################### # 函数:清理临时文件 ############################################################################### cleanup() { log_step "7/8" "清理临时文件" local ssh_options="" if [ -n "$SSH_KEY_PATH" ]; then ssh_options="-i $SSH_KEY_PATH" fi if [ -n "$SERVER_PORT" ]; then ssh_options="$ssh_options -p $SERVER_PORT" fi ssh $ssh_options "$SERVER_USER@$SERVER_HOST" "rm -rf /tmp/deploy-scripts" > /dev/null 2>&1 || true log_success "清理完成" echo "" } ############################################################################### # 函数:显示部署结果 ############################################################################### show_deployment_result() { log_step "8/8" "部署完成" local minutes=$((DEPLOY_DURATION / 60)) local seconds=$((DEPLOY_DURATION % 60)) echo "" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ 🎉 部署成功! ║" echo "╚════════════════════════════════════════════════════════════════╝" echo "" echo -e "${BOLD}部署信息:${NC}" echo " 版本: $COMMIT_HASH" echo " 分支: $DEPLOY_BRANCH" echo " 提交: $COMMIT_MESSAGE" echo " 作者: $COMMIT_AUTHOR" echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')" echo " 耗时: ${minutes}分${seconds}秒" echo "" echo -e "${BOLD}访问地址:${NC}" echo " https://valuefrontier.cn" echo "" echo "════════════════════════════════════════════════════════════════" echo "" } ############################################################################### # 主函数 ############################################################################### main() { echo "" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ VF React - 生产环境部署工具 ║" echo "╚════════════════════════════════════════════════════════════════╝" echo "" # 加载配置 load_config # 检查本地 Git 状态 check_local_git # 显示部署预览 show_deploy_preview # 测试 SSH 连接 test_ssh_connection # 上传服务器端脚本 upload_server_scripts # 执行远程部署 execute_remote_deployment # 发送成功通知 send_success_notification # 清理临时文件 cleanup # 显示部署结果 show_deployment_result } # 错误处理 trap 'log_error "部署过程中发生错误"; send_failure_notification "部署异常中断"; exit 1' ERR # 执行主函数 main "$@"