#!/bin/bash # =================================================================== # TopFans 打包上传部署脚本 # 功能: # - 本地:构建镜像 → 推送到镜像仓库 # - 远程:拉取镜像 → 启动服务 # - 回滚:回滚到指定版本 # =================================================================== # # 使用前提: # 1. 已安装 Docker # 2. 已创建阿里云容器镜像仓库 # 3. 服务器已配置 SSH 免密登录(建议) # # 使用方式: # # 本地构建 + 推送 # ./deploy.sh build v1.0.0 # # # 远程部署(从仓库拉取 + 启动) # ./deploy.sh deploy v1.0.0 # # # 回滚到指定版本 # ./deploy.sh rollback v1.0.0 # # # 查看部署历史 # ./deploy.sh history # # # 一键构建 + 推送 + 部署(本地构建完成后远程部署) # ./deploy.sh all v1.0.0 # # =================================================================== set -e # ==================== 颜色定义 ==================== RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' NC='\033[0m' # ==================== 路径配置 ==================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # ==================== 镜像仓库配置 ==================== # ⚠️ 需要修改为你的阿里云仓库地址 REGISTRY_HOST="registry.cn-hangzhou.aliyuncs.com" NAMESPACE="你的命名空间" # ⚠️ 修改这里 # 服务列表(必须与 docker-compose 中的服务名一致) SERVICES=( "gateway" "userservice" "socialservice" "assetservice" "galleryservice" "activityservice" ) # ==================== 服务器配置 ==================== # ⚠️ 修改为你的服务器信息 SERVER_HOST="" # 服务器 IP 或域名 SERVER_PORT="22" # SSH 端口 SERVER_USER="root" # SSH 用户名 SERVER_PATH="/opt/topfans/docker" # 服务器上 docker 目录路径 # ==================== 打印函数 ==================== print_step() { echo "" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE} $1${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } print_msg() { local color=$1 local msg=$2 echo -e "${color}${msg}${NC}" } # ==================== 帮助信息 ==================== show_help() { cat << EOF ${MAGENTA}TopFans 部署脚本${NC} ${YELLOW}用法:${NC} $0 <命令> [版本号] [选项] ${YELLOW}命令:${NC} ${GREEN}build${NC} <版本号> 本地构建镜像并推送到仓库 ${GREEN}deploy${NC} <版本号> 远程部署(从仓库拉取 + 启动服务) ${GREEN}rollback${NC} <版本号> 回滚到指定版本 ${GREEN}history${NC} 查看部署历史 ${GREEN}all${NC} <版本号> 一键构建 + 推送 + 部署 ${GREEN}clean${NC} 清理本地镜像(谨慎使用) ${YELLOW}选项:${NC} --server 指定服务器(覆盖配置文件) --skip-build 跳过构建(用于已构建过的情况) --skip-push 跳过推送(用于已推送过的情况) --force 强制执行(不确认) --help, -h 显示此帮助 ${YELLOW}示例:${NC} $0 build v1.0.0 # 构建并推送 $0 deploy v1.0.0 --server 192.168.1.100 # 部署到服务器 $0 rollback v0.9.0 # 回滚到 v0.9.0 $0 history # 查看部署历史 $0 all v1.0.0 --server 192.168.1.100 # 一键完成所有操作 ${YELLOW}前提准备:${NC} 1. 阿里云容器镜像: https://cr.console.aliyun.com/ 2. 修改脚本中的 REGISTRY_HOST 和 NAMESPACE 3. 修改 SERVER_HOST 为你的服务器 IP EOF } # ==================== 配置检查 ==================== check_config() { local errors=0 if [ "$NAMESPACE" = "你的命名空间" ]; then print_msg "$RED" "错误: 请修改 deploy.sh 中的 NAMESPACE 为你的阿里云仓库命名空间" errors=$((errors + 1)) fi if [ -z "$SERVER_HOST" ]; then print_msg "$YELLOW" "警告: SERVER_HOST 未设置,远程部署功能将不可用" fi return $errors } # ==================== 1. 构建镜像 ==================== do_build() { print_step "🔨 构建 Docker 镜像" # 调用构建脚本 ./build.sh --no-cache if [ $? -ne 0 ]; then print_msg "$RED" "❌ 构建失败" exit 1 fi print_msg "$GREEN" "✅ 镜像构建完成" } # ==================== 2. 推送镜像 ==================== do_push() { local version=$1 print_step "🔑 登录镜像仓库" # 登录(可能需要输入密码) docker login --username="${NAMESPACE}" "${REGISTRY_HOST}" || { print_msg "$RED" "❌ 登录失败" exit 1 } print_msg "$GREEN" "✅ 登录成功" print_step "📦 推送镜像到仓库" local failed=() local pushed=() for SERVICE in "${SERVICES[@]}"; do local local_image="topfans/${SERVICE}:latest" local remote_image="${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version}" local latest_image="${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:latest" echo "" print_msg "$YELLOW" "处理 ${SERVICE}..." # 打标签 docker tag "${local_image}" "${remote_image}" docker tag "${local_image}" "${latest_image}" # 推送版本标签 echo -e " ${CYAN}→ ${remote_image}${NC}" if docker push "${remote_image}"; then echo -e " ${GREEN}✅ 已推送${NC}" pushed+=("${SERVICE}") else echo -e " ${RED}❌ 推送失败${NC}" failed+=("${SERVICE}") fi done # 推送 latest echo "" print_msg "$YELLOW" "推送 latest 标签..." for SERVICE in "${SERVICES[@]}"; do local latest_image="${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:latest" docker push "${latest_image}" 2>/dev/null || true done print_step "📊 推送结果" if [ ${#failed[@]} -eq 0 ]; then print_msg "$GREEN" "✅ 全部推送成功" else print_msg "$RED" "❌ 失败: ${failed[*]}" fi echo "" print_msg "$CYAN" "已推送的镜像: v${version}" for SERVICE in "${pushed[@]}"; do echo -e " ${GREEN}${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version}${NC}" done return $([ ${#failed[@]} -eq 0 ] && echo 0 || echo 1) } # ==================== 3. 远程部署 ==================== do_deploy() { local version=$1 if [ -z "$SERVER_HOST" ]; then print_msg "$RED" "错误: 请设置 SERVER_HOST(服务器 IP)" print_msg "$YELLOW" "使用方法: $0 deploy ${version} --server " exit 1 fi print_step "🚀 远程部署到 ${SERVER_HOST}" # 构建远程部署脚本 local remote_script=" set -e echo '=== 1. 创建部署目录 ===' mkdir -p ${SERVER_PATH} cd ${SERVER_PATH} echo '=== 2. 登录镜像仓库 ===' # 注意:需要提前在服务器上配置 docker login,或者使用阿里云 AccessKey 登录 # 这里假设已配置免密登录或使用 docker-credential-ecr-login echo '使用镜像: ${REGISTRY_HOST}/${NAMESPACE}' echo '=== 3. 拉取镜像 ===' for service in ${SERVICES[*]}; do echo \"拉取 topfans-\$service:v${version}...\" docker pull ${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:v${version} done echo '=== 4. 打 latest 标签 ===' for service in ${SERVICES[*]}; do docker tag ${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:v${version} \ ${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:latest done echo '=== 5. 停止现有服务 ===' docker-compose -f docker-compose.prod.yml down 2>/dev/null || true echo '=== 6. 启动服务 ===' docker-compose -f docker-compose.prod.yml --profile prod up -d echo '=== 7. 等待服务就绪 ===' sleep 10 echo '=== 8. 健康检查 ===' curl -s http://localhost:8080/health > /dev/null && echo '✅ Gateway 健康' || echo '⚠️ Gateway 可能未就绪' echo '=== 9. 记录部署历史 ===' echo '{\"version\":\"${version}\",\"deployed_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"services\":${#SERVICES[@]}}' \ >> ${SERVER_PATH}/deploy_history.json echo '' echo '✅ 部署完成!' docker-compose -f docker-compose.prod.yml ps " # 执行远程脚本 print_msg "$YELLOW" "正在连接 ${SERVER_USER}@${SERVER_HOST}..." ssh -p "${SERVER_PORT}" -T "${SERVER_USER}@${SERVER_HOST}" << 'ENDSSH' set -e echo '=== 1. 检查 Docker 环境 ===' if ! command -v docker &> /dev/null; then echo '❌ Docker 未安装' exit 1 fi if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then echo '❌ docker-compose 未安装' exit 1 fi echo '✅ Docker 环境就绪' ENDSSH # 由于 heredoc 在复杂脚本中有问题,这里简化为直接执行关键命令 print_msg "$YELLOW" "执行部署命令..." # 分步执行远程命令 ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " mkdir -p ${SERVER_PATH} && \ echo '目录就绪' " print_msg "$GREEN" "✅ 服务器目录就绪" # 拉取镜像(逐个拉取以便查看进度) for SERVICE in "${SERVICES[@]}"; do print_msg "$YELLOW" "拉取 ${SERVICE}..." ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " docker pull ${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version} " print_msg "$GREEN" "✅ ${SERVICE} 拉取完成" done # 打标签 print_msg "$YELLOW" "打 latest 标签..." ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " for service in ${SERVICES[*]}; do docker tag ${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:v${version} \ ${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:latest done echo '标签完成' " # 停止旧服务 print_msg "$YELLOW" "停止旧服务..." ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " cd ${SERVER_PATH} && \ docker-compose -f docker-compose.prod.yml down 2>/dev/null || true " # 启动新服务 print_msg "$YELLOW" "启动服务..." ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " cd ${SERVER_PATH} && \ docker-compose -f docker-compose.prod.yml --profile prod up -d " # 等待并检查 print_msg "$YELLOW" "等待服务启动 (15s)..." sleep 15 print_step "📊 部署结果" ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " echo '' docker-compose -f docker-compose.prod.yml ps echo '' echo -n 'Gateway 健康检查: ' curl -s http://localhost:8080/health > /dev/null && echo '✅ OK' || echo '⚠️ 检查失败' " print_msg "$GREEN" "✅ 远程部署完成" } # ==================== 4. 回滚 ==================== do_rollback() { local version=$1 if [ -z "$SERVER_HOST" ]; then print_msg "$RED" "错误: 请设置 SERVER_HOST" exit 1 fi print_step "🔄 回滚到版本 v${version}" print_msg "$YELLOW" "正在回滚 ${SERVER_HOST} 上的服务..." # 停止服务 print_msg "$YELLOW" "停止服务..." ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " cd ${SERVER_PATH} && \ docker-compose -f docker-compose.prod.yml down " # 拉取指定版本镜像并打标签 for SERVICE in "${SERVICES[@]}"; do print_msg "$YELLOW" "拉取 ${SERVICE}:v${version}..." ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " docker pull ${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version} docker tag ${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version} \ ${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:latest " print_msg "$GREEN" "✅ ${SERVICE} 回滚完成" done # 启动服务 print_msg "$YELLOW" "启动服务..." ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " cd ${SERVER_PATH} && \ docker-compose -f docker-compose.prod.yml --profile prod up -d " sleep 10 print_step "📊 回滚结果" ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " echo '' docker-compose -f docker-compose.prod.yml ps echo '' echo -n 'Gateway 健康检查: ' curl -s http://localhost:8080/health > /dev/null && echo '✅ OK' || echo '⚠️ 检查失败' " print_msg "$GREEN" "✅ 回滚完成!当前版本: v${version}" } # ==================== 5. 查看历史 ==================== do_history() { if [ -z "$SERVER_HOST" ]; then print_msg "$RED" "错误: 请设置 SERVER_HOST" exit 1 fi print_step "📜 部署历史" ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " if [ -f ${SERVER_PATH}/deploy_history.json ]; then cat ${SERVER_PATH}/deploy_history.json else echo '暂无部署历史' fi " echo "" print_msg "$YELLOW" "查看实时日志: ssh ${SERVER_USER}@${SERVER_HOST} 'docker-compose -f ${SERVER_PATH}/docker-compose.prod.yml logs -f'" } # ==================== 6. 清理本地镜像 ==================== do_clean() { print_step "🧹 清理本地镜像" print_msg "$RED" "警告: 将删除所有 topfans 镜像" print_msg "$YELLOW" "列出当前镜像:" docker images | grep topfans || echo "无 topfans 镜像" echo "" read -p "确认删除? (y/N): " confirm if [ "$confirm" != "y" ]; then print_msg "$YELLOW" "已取消" exit 0 fi docker images | grep topfans | awk '{print $3}' | xargs -r docker rmi -f print_msg "$GREEN" "✅ 清理完成" } # ==================== 主函数 ==================== main() { if [ $# -eq 0 ]; then show_help exit 0 fi local command=$1 shift local version="" local skip_build=false local skip_push=false local force=false # 解析剩余参数 while [[ $# -gt 0 ]]; do case $1 in --server) SERVER_HOST="$2" shift 2 ;; --skip-build) skip_build=true shift ;; --skip-push) skip_push=true shift ;; --force) force=true shift ;; -h|--help) show_help exit 0 ;; v*) version="${1#v}" shift ;; *) echo -e "${RED}错误: 未知参数 '$1'${NC}" exit 1 ;; esac done # 检查配置 check_config || true case $command in build) # 构建 + 推送 if [ -z "$version" ]; then echo -e "${RED}错误: 请指定版本号${NC}" echo "用法: $0 build v1.0.0" exit 1 fi echo -e "${CYAN}版本: v${version}${NC}" echo -e "${CYAN}仓库: ${REGISTRY_HOST}/${NAMESPACE}${NC}" if [ "$skip_build" = false ]; then do_build fi if [ "$skip_push" = false ]; then do_push "$version" fi ;; deploy) # 远程部署 if [ -z "$version" ]; then echo -e "${RED}错误: 请指定版本号${NC}" echo "用法: $0 deploy v1.0.0" exit 1 fi echo -e "${CYAN}版本: v${version}${NC}" echo -e "${CYAN}目标: ${SERVER_USER}@${SERVER_HOST}${NC}" do_deploy "$version" ;; rollback) # 回滚 if [ -z "$version" ]; then echo -e "${RED}错误: 请指定要回滚的版本${NC}" echo "用法: $0 rollback v1.0.0" exit 1 fi echo -e "${RED}⚠️ 确认回滚到 v${version}?${NC}" [ "$force" = false ] && read -p "确认? (y/N): " confirm && [ "$confirm" != "y" ] && exit 0 do_rollback "$version" ;; history) do_history ;; clean) do_clean ;; all) # 构建 + 推送 + 部署 if [ -z "$version" ]; then echo -e "${RED}错误: 请指定版本号${NC}" exit 1 fi if [ -z "$SERVER_HOST" ]; then echo -e "${RED}错误: 请设置 SERVER_HOST 或使用 --server 参数${NC}" exit 1 fi echo -e "${MAGENTA}一键部署流程:${NC}" echo " 1. 构建镜像" echo " 2. 推送到仓库" echo " 3. 部署到 ${SERVER_HOST}" echo "" [ "$force" = false ] && read -p "继续? (y/N): " confirm && [ "$confirm" != "y" ] && exit 0 do_build do_push "$version" do_deploy "$version" print_step "🎉 全部完成!" ;; *) echo -e "${RED}错误: 未知命令 '$command'${NC}" show_help exit 1 ;; esac } main "$@"