topfans/docker/deploy.sh
2026-04-08 13:43:11 +08:00

531 lines
15 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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"
# ==================== 镜像配置 ====================
# 本地构建后打包传输到服务器,不需要镜像仓库
SERVICES=(
"gateway"
"userservice"
"socialservice"
"assetservice"
"galleryservice"
"activityservice"
)
# ==================== 服务器配置 ====================
# ⚠️ 修改为你的服务器信息
SERVER_HOST="101.132.250.62" # 服务器 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 <IP> 指定服务器(覆盖配置文件)
--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. 修改 SERVER_HOST 为你的服务器 IP
2. 配置服务器 SSH 免密登录(建议)
EOF
}
# ==================== 配置检查 ====================
check_config() {
local errors=0
if [ -z "$SERVER_HOST" ]; then
print_msg "$YELLOW" "警告: SERVER_HOST 未设置,远程部署功能将不可用"
fi
return $errors
}
# ==================== 1. 构建镜像 ====================
do_build() {
print_step "🔨 构建 Docker 镜像"
# 调用构建脚本
# ./build.sh --no-cache
./build.sh
if [ $? -ne 0 ]; then
print_msg "$RED" "❌ 构建失败"
exit 1
fi
print_msg "$GREEN" "✅ 镜像构建完成"
}
# ==================== 2. 打包并传输镜像到服务器 ====================
do_push() {
local version=$1
if [ -z "$SERVER_HOST" ]; then
print_msg "$RED" "错误: 请设置 SERVER_HOST"
exit 1
fi
print_step "📦 打包镜像为 tar 文件"
local tmp_dir="/tmp/topfans-images-${version}"
mkdir -p "${tmp_dir}"
local failed=()
local packed=()
for SERVICE in "${SERVICES[@]}"; do
local local_image="topfans/${SERVICE}:latest"
local tar_file="${tmp_dir}/${SERVICE}.tar"
echo ""
print_msg "$YELLOW" "处理 ${SERVICE}..."
if docker save "${local_image}" -o "${tar_file}"; then
echo -e " ${GREEN}✅ 已打包${NC}"
packed+=("${SERVICE}")
else
echo -e " ${RED}❌ 打包失败${NC}"
failed+=("${SERVICE}")
fi
done
if [ ${#failed[@]} -ne 0 ]; then
print_msg "$RED" "❌ 打包失败: ${failed[*]}"
rm -rf "${tmp_dir}"
exit 1
fi
print_msg "$GREEN" "✅ 全部打包完成"
print_step "📤 传输镜像到服务器"
print_msg "$YELLOW" "正在传输到 ${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}..."
# 创建服务器目录
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "mkdir -p ${SERVER_PATH}/images && rm -f ${SERVER_PATH}/images/*.tar 2>/dev/null || true"
# 传输 tar 文件
for SERVICE in "${packed[@]}"; do
local tar_file="${tmp_dir}/${SERVICE}.tar"
print_msg "$YELLOW" "传输 ${SERVICE}.tar..."
scp -P "${SERVER_PORT}" "${tar_file}" "${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/images/"
print_msg "$GREEN" "${SERVICE}.tar 传输完成"
done
# 清理本地临时文件
rm -rf "${tmp_dir}"
print_msg "$GREEN" "✅ 镜像传输完成"
}
# ==================== 3. 远程部署 ====================
do_deploy() {
local version=$1
if [ -z "$SERVER_HOST" ]; then
print_msg "$RED" "错误: 请设置 SERVER_HOST服务器 IP"
print_msg "$YELLOW" "使用方法: $0 deploy ${version} --server <IP>"
exit 1
fi
print_step "🚀 远程部署到 ${SERVER_HOST}"
print_msg "$YELLOW" "检查 Docker 环境..."
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
# 确保服务器目录存在
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "mkdir -p ${SERVER_PATH}/images"
print_msg "$GREEN" "✅ 服务器目录就绪"
# 从 tar 文件加载镜像
print_step "📥 从 tar 文件加载镜像"
for SERVICE in "${SERVICES[@]}"; do
print_msg "$YELLOW" "加载 ${SERVICE}..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
docker load -i ${SERVER_PATH}/images/${SERVICE}.tar
"
print_msg "$GREEN" "${SERVICE} 加载完成"
done
# 打 latest 标签
print_msg "$YELLOW" "打 latest 标签..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
for service in ${SERVICES[*]}; do
docker tag topfans/\${service}:latest topfans/\${service}:v${version}
docker tag topfans/\${service}:latest 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
"
# 从已有的 tar 文件加载镜像并打标签
for SERVICE in "${SERVICES[@]}"; do
print_msg "$YELLOW" "加载 ${SERVICE}:v${version}..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
docker load -i ${SERVER_PATH}/images/${SERVICE}.tar
docker tag topfans/${SERVICE}:latest topfans/${SERVICE}:v${version}
docker tag topfans/${SERVICE}:latest 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}目标: ${SERVER_USER}@${SERVER_HOST}${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 "$@"