#!/bin/bash # 自动部署脚本 - 部署到 1Panel Docker 容器 # 设计原则:拉取式部署,只在服务器本地执行,CI 不通过 SSH 触发 # 使用方法: ./deploy.sh # deploy_package_path: 部署包路径(如 /ci-artifacts/server-deploy-latest.tar.gz) set -e DEPLOY_PACKAGE="$1" CONTAINER_NAME="${CONTAINER_NAME:-lunar-server}" DEPLOY_DIR="${DEPLOY_DIR:-/opt/lunar/production}" BACKUP_DIR="${BACKUP_DIR:-/opt/lunar/backups}" HEALTH_URL="${HEALTH_URL:-http://localhost:8080/api/version}" # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 回滚函数:恢复 current.old 并重启容器 rollback() { if [ -d "$DEPLOY_DIR/current.old" ]; then rm -rf "$DEPLOY_DIR/current" mv "$DEPLOY_DIR/current.old" "$DEPLOY_DIR/current" docker restart "$CONTAINER_NAME" || log_error "回滚后容器重启失败,需人工介入" log_warn "已回滚到上一版本" else log_error "无可回滚版本,需人工介入" fi } # 检查部署包是否存在 if [ ! -f "$DEPLOY_PACKAGE" ]; then log_error "部署包不存在: $DEPLOY_PACKAGE" exit 1 fi log_info "开始自动部署..." log_info "部署包: $DEPLOY_PACKAGE" log_info "目标容器: $CONTAINER_NAME" log_info "部署目录: $DEPLOY_DIR" # 创建必要的目录 mkdir -p "$DEPLOY_DIR" mkdir -p "$BACKUP_DIR" # 备份当前版本 TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="$BACKUP_DIR/backup_${TIMESTAMP}.tar.gz" if [ -d "$DEPLOY_DIR/current" ]; then log_info "备份当前版本到: $BACKUP_FILE" tar -czf "$BACKUP_FILE" -C "$DEPLOY_DIR" current/ || log_warn "备份失败,继续部署" fi # 解压新版本到暂存目录(不碰 current,避免部署空窗) log_info "解压部署包..." TEMP_DIR=$(mktemp -d) tar -xzf "$DEPLOY_PACKAGE" -C "$TEMP_DIR" # 检查解压后的目录结构 if [ ! -d "$TEMP_DIR/deploy" ]; then log_error "部署包结构错误:缺少 deploy 目录" rm -rf "$TEMP_DIR" exit 1 fi # 校验二进制存在 if [ ! -f "$TEMP_DIR/deploy/bin/server" ]; then log_error "部署包缺少 bin/server 二进制" rm -rf "$TEMP_DIR" exit 1 fi chmod +x "$TEMP_DIR/deploy/bin/server" # 部署新版本(目录级交换,接近原子) log_info "部署新版本..." mv "$TEMP_DIR/deploy" "$DEPLOY_DIR/current.new" rm -rf "$DEPLOY_DIR/current.old" if [ -d "$DEPLOY_DIR/current" ]; then mv "$DEPLOY_DIR/current" "$DEPLOY_DIR/current.old" fi mv "$DEPLOY_DIR/current.new" "$DEPLOY_DIR/current" rm -rf "$TEMP_DIR" # 检查 Docker 容器是否存在 if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then log_error "Docker 容器不存在: $CONTAINER_NAME" log_info "请在 1Panel 中创建容器,或使用以下命令:" echo "" echo "docker run -d \\" echo " --name $CONTAINER_NAME \\" echo " -v $DEPLOY_DIR/current:/app \\" echo " -p 8080:8080 \\" echo " --restart unless-stopped \\" echo " golang:1.22-bookworm \\" echo " /app/bin/server" echo "" exit 1 fi # 重启 Docker 容器 log_info "重启 Docker 容器: $CONTAINER_NAME" if docker restart "$CONTAINER_NAME"; then log_info "✅ 容器重启成功" else log_error "❌ 容器重启失败,回滚到上一版本" rollback exit 1 fi # 健康检查:轮询 HTTP 接口确认服务真正可用 log_info "执行健康检查: $HEALTH_URL" HEALTHY=0 for i in $(seq 1 10); do sleep 2 if curl -fsS --max-time 5 "$HEALTH_URL" > /dev/null 2>&1; then HEALTHY=1 break fi done if [ "$HEALTHY" = "1" ]; then log_info "✅ 健康检查通过" curl -fsS --max-time 5 "$HEALTH_URL" && echo "" rm -rf "$DEPLOY_DIR/current.old" else log_error "❌ 健康检查失败,回滚到上一版本" docker logs --tail 50 "$CONTAINER_NAME" || true rollback exit 1 fi # 清理旧备份(保留最近 5 个) log_info "清理旧备份..." ls -t "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f log_info "✅ 部署完成!" log_info "备份文件: $BACKUP_FILE" log_info "部署目录: $DEPLOY_DIR/current" # 输出部署信息(用于通知) echo "" echo "DEPLOY_SUCCESS=true" echo "DEPLOY_TIME=$TIMESTAMP" echo "CONTAINER_NAME=$CONTAINER_NAME"