347 lines
9.3 KiB
Bash
Executable File
347 lines
9.3 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# ===========================================
|
||
# Rust Backend Deployment Script
|
||
# ===========================================
|
||
# Usage: ./deploy.sh [development|production] [options]
|
||
#
|
||
# Options:
|
||
# --dry-run 预览模式,不执行实际操作
|
||
# --yes, -y 跳过确认提示
|
||
# --skip-tests 跳过部署后测试
|
||
# ===========================================
|
||
|
||
set -euo pipefail
|
||
|
||
PROJECT_NAME="rust-backend"
|
||
REMOTE_USER="root"
|
||
REMOTE_HOST="1panel-server"
|
||
APP_ENV="${1:-production}"
|
||
|
||
# 默认选项
|
||
DRY_RUN=false
|
||
SKIP_CONFIRM=false
|
||
SKIP_TESTS=false
|
||
|
||
# 解析选项
|
||
shift 2>/dev/null || true
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--dry-run)
|
||
DRY_RUN=true
|
||
;;
|
||
--yes|-y)
|
||
SKIP_CONFIRM=true
|
||
;;
|
||
--skip-tests)
|
||
SKIP_TESTS=true
|
||
;;
|
||
--help|-h)
|
||
echo "用法: $0 [development|production] [options]"
|
||
echo ""
|
||
echo "选项:"
|
||
echo " --dry-run 预览模式,不执行实际操作"
|
||
echo " --yes, -y 跳过确认提示"
|
||
echo " --skip-tests 跳过部署后测试"
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo "未知选项: $1"
|
||
exit 1
|
||
;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
# 根据环境选择远程目录和服务
|
||
case "${APP_ENV}" in
|
||
development)
|
||
REMOTE_DIR="/root/rust/rust_backend_dev"
|
||
SERVICE_NAME="rust-backend-dev.service"
|
||
TEST_DOMAIN="https://xmclassmate.top/dev"
|
||
;;
|
||
production)
|
||
REMOTE_DIR="/root/rust/rust_backend"
|
||
SERVICE_NAME="rust-backend.service"
|
||
TEST_DOMAIN="https://xmclassmate.top"
|
||
;;
|
||
*)
|
||
echo "无效的环境: ${APP_ENV}"
|
||
echo "支持的環境: development, production"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
# 彩色输出
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m'
|
||
|
||
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
|
||
log_dry() { echo -e "${YELLOW}[DRY-RUN]${NC} $1"; }
|
||
|
||
# 获取 Git 版本信息
|
||
get_git_info() {
|
||
local git_branch
|
||
local git_commit
|
||
local git_version
|
||
|
||
git_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||
git_commit=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||
git_version="${git_branch}@${git_commit}"
|
||
|
||
echo "$git_version"
|
||
}
|
||
|
||
# 检查依赖
|
||
check_dependencies() {
|
||
log_step "检查依赖..."
|
||
local deps=("cargo" "rsync" "ssh")
|
||
for cmd in "${deps[@]}"; do
|
||
if ! command -v "$cmd" &> /dev/null; then
|
||
log_error "依赖命令 '$cmd' 未找到,请先安装。"
|
||
fi
|
||
done
|
||
log_info "依赖检查通过"
|
||
}
|
||
|
||
# 获取远程服务器 SSH ControlMaster socket 路径
|
||
get_ssh_socket() {
|
||
echo "/tmp/ssh-master-${REMOTE_HOST}-$$"
|
||
}
|
||
|
||
# 建立 SSH 连接(复用)
|
||
ssh_connect() {
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log_dry "ssh -o ControlMaster=auto -o ControlPath=$(get_ssh_socket) -o ControlPersist=5 ${REMOTE_USER}@${REMOTE_HOST}"
|
||
return 0
|
||
fi
|
||
|
||
local socket
|
||
socket=$(get_ssh_socket)
|
||
|
||
# 检查是否已有连接
|
||
if ! ssh -o "ControlPath=${socket}" -o "ConnectionAttempts=3" -o "ConnectTimeout=10" \
|
||
-o "ControlMaster=no" -o "ControlPersist=no" \
|
||
"${REMOTE_USER}@${REMOTE_HOST}" "echo 'connection test'" &>/dev/null; then
|
||
|
||
log_info "建立 SSH 连接..."
|
||
ssh -o ControlMaster=auto -o ControlPath="${socket}" -o ControlPersist=600 \
|
||
-o ConnectionAttempts=3 -o ConnectTimeout=10 \
|
||
-N "${REMOTE_USER}@${REMOTE_HOST}" &
|
||
sleep 2
|
||
fi
|
||
}
|
||
|
||
# 关闭 SSH 连接
|
||
ssh_disconnect() {
|
||
if [ "$DRY_RUN" = true ]; then
|
||
return 0
|
||
fi
|
||
|
||
local socket
|
||
socket=$(get_ssh_socket)
|
||
ssh -o "ControlPath=${socket}" -O exit "${REMOTE_USER}@${REMOTE_HOST}" 2>/dev/null || true
|
||
}
|
||
|
||
# 编译项目
|
||
build_project() {
|
||
log_step "编译项目 (release模式)..."
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log_dry "APP_ENV=\"${APP_ENV}\" cargo build --release"
|
||
return 0
|
||
fi
|
||
|
||
if APP_ENV="${APP_ENV}" cargo build --release; then
|
||
log_info "编译成功"
|
||
else
|
||
log_error "编译失败!"
|
||
fi
|
||
}
|
||
|
||
# 备份旧版本
|
||
backup_old_version() {
|
||
log_step "备份旧版本..."
|
||
local binary_path="./target/release/${PROJECT_NAME}"
|
||
local timestamp
|
||
timestamp=$(date +%Y%m%d_%H%M%S)
|
||
local backup_path="${REMOTE_DIR}/${PROJECT_NAME}.backup.${timestamp}"
|
||
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'if [ -f ${REMOTE_DIR}/${PROJECT_NAME} ]; then cp ${REMOTE_DIR}/${PROJECT_NAME} ${backup_path}; fi'"
|
||
return 0
|
||
fi
|
||
|
||
ssh "${REMOTE_USER}@${REMOTE_HOST}" "
|
||
if [ -f '${REMOTE_DIR}/${PROJECT_NAME}' ]; then
|
||
cp '${REMOTE_DIR}/${PROJECT_NAME}' '${backup_path}'
|
||
echo '已备份到: ${backup_path}'
|
||
else
|
||
echo '无旧版本需要备份'
|
||
fi
|
||
"
|
||
log_info "备份完成"
|
||
}
|
||
|
||
# 上传二进制文件
|
||
upload_binary() {
|
||
log_step "上传二进制文件..."
|
||
local binary_path="./target/release/${PROJECT_NAME}"
|
||
|
||
if [ ! -f "$binary_path" ]; then
|
||
log_error "编译产物未找到: $binary_path"
|
||
fi
|
||
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log_dry "rsync -avzP '$binary_path' '${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/'"
|
||
return 0
|
||
fi
|
||
|
||
rsync -avzP --progress "$binary_path" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
|
||
log_info "上传完成"
|
||
}
|
||
|
||
# 上传配置文件(只上传 .toml 文件)
|
||
upload_config() {
|
||
log_step "上传配置文件..."
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log_dry "rsync -avzP --include='*.toml' --exclude='*' ./config/ '${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/config/'"
|
||
return 0
|
||
fi
|
||
|
||
rsync -avzP --include='*.toml' --exclude='*' ./config/ "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/config/"
|
||
log_info "配置文件上传完成"
|
||
}
|
||
|
||
# 上传测试脚本
|
||
upload_test_script() {
|
||
log_step "上传测试脚本..."
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log_dry "rsync -avzP ./test_deployment.sh '${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/'"
|
||
return 0
|
||
fi
|
||
|
||
rsync -avzP --progress ./test_deployment.sh "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
|
||
ssh "${REMOTE_USER}@${REMOTE_HOST}" "chmod +x ${REMOTE_DIR}/test_deployment.sh"
|
||
log_info "测试脚本上传完成"
|
||
}
|
||
|
||
# 重启远程服务
|
||
restart_service() {
|
||
log_step "重启服务 '${SERVICE_NAME}'..."
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'systemctl restart ${SERVICE_NAME}'"
|
||
return 0
|
||
fi
|
||
|
||
if ssh "${REMOTE_USER}@${REMOTE_HOST}" "systemctl restart ${SERVICE_NAME}"; then
|
||
log_info "服务重启成功"
|
||
else
|
||
log_error "服务重启失败!请检查服务状态和日志。"
|
||
fi
|
||
|
||
# 等待服务启动
|
||
sleep 3
|
||
}
|
||
|
||
# 运行部署后测试
|
||
run_tests() {
|
||
log_step "执行部署后测试..."
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'TEST_DOMAIN=\"${TEST_DOMAIN}\" ${REMOTE_DIR}/test_deployment.sh'"
|
||
return 0
|
||
fi
|
||
|
||
if [ "$SKIP_TESTS" = true ]; then
|
||
log_warn "跳过测试(--skip-tests)"
|
||
return 0
|
||
fi
|
||
|
||
if ssh "${REMOTE_USER}@${REMOTE_HOST}" "TEST_DOMAIN=\"${TEST_DOMAIN}\" ${REMOTE_DIR}/test_deployment.sh"; then
|
||
log_info "测试通过"
|
||
else
|
||
log_error "测试失败!请检查服务状态。"
|
||
fi
|
||
}
|
||
|
||
# 确认部署
|
||
confirm_deploy() {
|
||
if [ "$SKIP_CONFIRM" = true ]; then
|
||
return 0
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${YELLOW}========== 部署确认 ==========${NC}"
|
||
echo -e " 环境: ${APP_ENV}"
|
||
echo -e " 远程目录: ${REMOTE_DIR}"
|
||
echo -e " 服务: ${SERVICE_NAME}"
|
||
echo -e " Git: ${GIT_VERSION}"
|
||
echo -e " 测试域名: ${TEST_DOMAIN}"
|
||
echo -e "================================${NC}"
|
||
echo ""
|
||
|
||
read -p "确认部署? [y/N] " -n 1 -r
|
||
echo
|
||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||
log_warn "部署已取消"
|
||
exit 0
|
||
fi
|
||
}
|
||
|
||
# 显示部署信息
|
||
show_deploy_info() {
|
||
echo ""
|
||
echo -e "${BLUE}========================================${NC}"
|
||
echo -e "${BLUE} Rust Backend 部署脚本${NC}"
|
||
echo -e "${BLUE}========================================${NC}"
|
||
echo -e " 项目: ${PROJECT_NAME}"
|
||
echo -e " 环境: ${APP_ENV}"
|
||
echo -e " 远程主机: ${REMOTE_USER}@${REMOTE_HOST}"
|
||
echo -e " 远程目录: ${REMOTE_DIR}"
|
||
echo -e " 服务: ${SERVICE_NAME}"
|
||
echo -e " Git 版本: ${GIT_VERSION}"
|
||
if [ "$DRY_RUN" = true ]; then
|
||
echo -e " 模式: ${YELLOW}预览模式 (DRY-RUN)${NC}"
|
||
fi
|
||
echo -e "${BLUE}========================================${NC}"
|
||
echo ""
|
||
}
|
||
|
||
# 清理函数
|
||
cleanup() {
|
||
ssh_disconnect
|
||
}
|
||
|
||
trap cleanup EXIT
|
||
|
||
# 主函数
|
||
main() {
|
||
GIT_VERSION=$(get_git_info)
|
||
|
||
show_deploy_info
|
||
confirm_deploy
|
||
|
||
check_dependencies
|
||
ssh_connect
|
||
build_project
|
||
backup_old_version
|
||
upload_binary
|
||
upload_config
|
||
upload_test_script
|
||
restart_service
|
||
run_tests
|
||
|
||
echo ""
|
||
log_info "========== 部署完成! =========="
|
||
echo -e " Git: ${GIT_VERSION}"
|
||
echo -e " 环境: ${APP_ENV}"
|
||
echo ""
|
||
}
|
||
|
||
main
|