test-backend/deploy/deploy.sh

1279 lines
43 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
# ============================================
# 多模式部署脚本
# 用法:
# bash deploy.sh init 交互式初始化 .env
# bash deploy.sh start 启动
# bash deploy.sh build [分支] 编译镜像
# bash deploy.sh rebuild [分支] 拉取代码 + 编译 + 启动
# bash deploy.sh stop 停止
# bash deploy.sh logs 查看日志
# bash deploy.sh status 容器状态
# bash deploy.sh config 检查配置
# bash deploy.sh init-nacos 初始化 Nacos 配置(仅 Cloud 模式)
# ============================================
set -e
DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$DIR"
PROJECT_DIR="$(dirname "$DIR")"
CMD="${1:-help}"
BRANCH="${2:-master}"
# ============================================
# 工具函数
# ============================================
# 生成随机 JWT 密钥
gen_jwt_secret() {
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 48
}
# TCP 端口连通性检查
check_port() {
local host="$1" port="$2" label="$3"
echo -n " ${label} (${host}:${port}) ... "
if command -v nc &>/dev/null; then
if nc -z -w 5 "$host" "$port" 2>/dev/null; then
echo "✅ 可达"
return 0
fi
elif command -v timeout &>/dev/null; then
if timeout 5 bash -c "echo >/dev/tcp/${host}/${port}" 2>/dev/null; then
echo "✅ 可达"
return 0
fi
else
# macOS 没有 timeout用 perl 做超时
if perl -e "use IO::Socket::INET; \$s=IO::Socket::INET->new(PeerAddr=>'$host',PeerPort=>$port,Timeout=>5); exit(1) unless \$s;" 2>/dev/null; then
echo "✅ 可达"
return 0
fi
fi
echo "❌ 不可达"
return 1
}
# MySQL 连接 + 库存在测试
check_mysql_connect_and_db() {
local host="$1" port="$2" user="$3" pass="$4" db_name="$5"
echo -n " MySQL 连接测试 (${user}@${host}:${port}/${db_name}) ... "
# 确定用哪个 mysql 客户端
local mysql_cmd=""
local connect_host="${host}"
if command -v mysql &>/dev/null; then
mysql_cmd="mysql -h${host} -P${port} -u${user} -p${pass}"
else
local docker_opts="--network host"
if [ "${host}" = "127.0.0.1" ] || [ "${host}" = "localhost" ]; then
if ! docker info 2>/dev/null | grep -q "OSType: linux"; then
connect_host="host.docker.internal"
docker_opts=""
fi
fi
mysql_cmd="docker run --rm ${docker_opts} mysql:8.0 mysql -h${connect_host} -P${port} -u${user} -p${pass}"
fi
# 第一步:基础连接测试
if ! $mysql_cmd -e "SELECT 1" 2>/dev/null >/dev/null; then
echo "❌ 连接失败(用户名/密码/网络)"
return 1
fi
# 第二步:库存在测试
if $mysql_cmd -e "USE ${db_name}" 2>/dev/null >/dev/null; then
echo "✅ 连接成功,库 '${db_name}' 存在"
return 0
fi
echo "⚠️ 连接成功,但库 '${db_name}' 不存在"
return 2
}
# Redis 连接测试
check_redis_connect() {
local host="$1" port="$2" pass="$3"
echo -n " Redis 连接测试 (${host}:${port}) ... "
local redis_cmd=""
local connect_host="${host}"
if command -v redis-cli &>/dev/null; then
redis_cmd="redis-cli -h ${host} -p ${port}"
else
local docker_opts="--network host"
if [ "${host}" = "127.0.0.1" ] || [ "${host}" = "localhost" ]; then
if ! docker info 2>/dev/null | grep -q "OSType: linux"; then
connect_host="host.docker.internal"
docker_opts=""
fi
fi
redis_cmd="docker run --rm ${docker_opts} redis:7-alpine redis-cli -h ${connect_host} -p ${port}"
fi
if [ -n "$pass" ]; then
${redis_cmd} -a "${pass}" --no-auth-warning PING 2>/dev/null | grep -q "PONG" && {
echo "✅ 连接成功"; return 0
}
else
${redis_cmd} PING 2>/dev/null | grep -q "PONG" && {
echo "✅ 连接成功"; return 0
}
fi
echo "❌ 连接失败"
return 1
}
# MinIO 连接测试
check_minio_connect() {
local host="$1" port="$2" access_key="$3" secret_key="$4"
echo -n " MinIO 连接测试 (${host}:${port}) ... "
if curl -s -o /dev/null -w "%{http_code}" "http://${host}:${port}/minio/health/live" 2>/dev/null | grep -q "200"; then
echo "✅ 可达"
return 0
fi
echo "❌ 不可达"
return 1
}
# 根据 MODE 变量检查 external 服务连通性
check_connectivity() {
local errors=0
echo ""
echo ">>> 检查外接服务连通性..."
if [ "${MYSQL_MODE}" = "external" ]; then
check_port "${MYSQL_HOST}" "${MYSQL_PORT}" "MySQL" || errors=$((errors + 1))
fi
if [ "${REDIS_MODE}" = "external" ]; then
check_port "${REDIS_HOST}" "${REDIS_PORT}" "Redis" || errors=$((errors + 1))
fi
if [ "${MINIO_MODE}" = "external" ]; then
check_port "${MINIO_HOST}" "${MINIO_PORT}" "MinIO" || errors=$((errors + 1))
fi
return $errors
}
# 数据库初始化(通过 mysql 客户端执行 SQL
do_init_db() {
load_env
if [ "${MYSQL_MODE}" != "external" ]; then
echo " MYSQL_MODE=${MYSQL_MODE},使用 Docker MySQL容器首次启动时自动初始化无需手动执行。"
return 0
fi
if [ "${DB_INIT}" != "true" ]; then
echo " DB_INIT=false跳过数据库初始化。"
echo " 如需手动初始化:"
echo " 1. 在 .env 中设置 DB_INIT=true"
echo " 2. 将 ../db/ghb_base_init.sql 导入 ${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}"
return 0
fi
local sql_file="${DIR}/../db/ghb_base_init.sql"
if [ ! -f "$sql_file" ]; then
echo "❌ SQL 文件不存在: $sql_file"
exit 1
fi
echo ""
echo "========================================="
echo " 数据库初始化"
echo "========================================="
echo " 目标: ${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}"
echo " SQL: ${sql_file}"
echo "========================================="
echo ""
echo ">>> 检查 MySQL 连通性..."
if ! check_port "${MYSQL_HOST}" "${MYSQL_PORT}" "MySQL"; then
echo "❌ MySQL 不可达,无法初始化数据库"
exit 1
fi
echo ">>> 正在执行建表脚本..."
# 优先使用宿主机 mysql 客户端
if command -v mysql &>/dev/null; then
mysql -h"${MYSQL_HOST}" -P"${MYSQL_PORT}" -u"${MYSQL_USER}" -p"${MYSQL_PASS}" "${DB_NAME}" < "$sql_file" 2>&1
local rc=$?
else
# 通过 Docker 运行 mysql 客户端,兼容 Machost.docker.internal和 Linux--network host
local connect_host="${MYSQL_HOST}"
local network_mode="--network host"
if [ "${MYSQL_HOST}" = "127.0.0.1" ] || [ "${MYSQL_HOST}" = "localhost" ]; then
# Docker Desktop Mac/Windows 用 host.docker.internal
if docker info 2>/dev/null | grep -q "OSType: linux"; then
network_mode="--network host"
else
connect_host="host.docker.internal"
network_mode=""
fi
fi
cat "$sql_file" | docker run --rm -i $network_mode \
-v "$(dirname "$sql_file"):/sql:ro" \
mysql:8.0 \
mysql -h"${connect_host}" -P"${MYSQL_PORT}" -u"${MYSQL_USER}" -p"${MYSQL_PASS}" "${DB_NAME}" 2>&1
local rc=$?
fi
if [ $rc -eq 0 ]; then
echo ""
echo "✅ 数据库初始化完成"
echo ""
echo ">>> 为防止重复初始化,请在 .env 中设置 DB_INIT=false"
# 自动关闭初始化开关,防止重复执行
if [ -f "${DIR}/.env" ]; then
sed -i.bak 's/^DB_INIT=true$/DB_INIT=false/' "${DIR}/.env" && rm -f "${DIR}/.env.bak"
echo " 已自动设置 DB_INIT=false"
fi
else
echo ""
echo "❌ 数据库初始化失败"
exit 1
fi
}
# 检查 .env 是否存在
check_env() {
if [ ! -f "${DIR}/.env" ]; then
echo "❌ 未找到 .env请先执行bash deploy.sh init"
exit 1
fi
}
# 加载 .env
load_env() {
check_env
set -a
source "${DIR}/.env"
set +a
}
# 获取 compose 命令(根据 MODE 变量自动附加 --profile
get_compose_cmd() {
local compose_file="${COMPOSE_FILE:-docker-compose.yml}"
local cmd="docker compose -f ${DIR}/${compose_file}"
local profiles=""
[ "${MYSQL_MODE:-docker}" = "docker" ] && profiles="${profiles},mysql"
[ "${REDIS_MODE:-docker}" = "docker" ] && profiles="${profiles},redis"
[ "${MINIO_MODE:-none}" = "docker" ] && profiles="${profiles},minio"
[ "${FRONTEND_ENABLED:-false}" = "true" ] && profiles="${profiles},frontend"
profiles="${profiles#,}" # 去掉开头的逗号
if [ -n "$profiles" ]; then
for p in ${profiles//,/ }; do
cmd="${cmd} --profile $p"
done
fi
echo "$cmd"
}
# 等待服务健康
wait_for_healthy() {
local container="$1" label="$2" max_wait="${3:-60}"
echo -n ">>> 等待 ${label} 就绪..."
for i in $(seq 1 "$max_wait"); do
local status
status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "unknown")
if [ "$status" = "healthy" ]; then
echo " ✅"
return 0
fi
sleep 2
done
echo " ❌ 超时"
return 1
}
# Docker MySQL 启动后验证库是否存在
verify_mysql_db() {
local container="${APP_NAME:-test}-mysql"
local db_name="${DB_NAME:-ghb_base}"
local pass="${MYSQL_ROOT_PASSWORD:-ChangeMe123}"
echo -n ">>> 验证数据库 '${db_name}' ... "
if docker exec "$container" mysql -uroot -p"${pass}" -e "USE ${db_name}" 2>/dev/null >/dev/null; then
echo "✅ 已就绪"
return 0
else
echo "⚠️ 库 '${db_name}' 不存在,请检查 MySQL 初始化"
return 1
fi
}
# Docker Redis 启动后验证连通性
verify_redis() {
local container="${APP_NAME:-test}-redis"
local pass="${REDIS_PASS:-}"
echo -n ">>> 验证 Redis 连接 ... "
if [ -n "$pass" ]; then
docker exec "$container" redis-cli -a "${pass}" --no-auth-warning PING 2>/dev/null | grep -q "PONG"
else
docker exec "$container" redis-cli PING 2>/dev/null | grep -q "PONG"
fi
if [ $? -eq 0 ]; then
echo "✅ 已就绪"
return 0
else
echo "⚠️ Redis 未就绪"
return 1
fi
}
# ============================================
# init — 交互式初始化 .env
# ============================================
do_init() {
echo ""
echo "========================================="
echo " ghb-base 部署配置向导"
echo "========================================="
echo ""
# === 一级选择:部署架构 ===
echo "请选择部署架构:"
echo " 1. 单体"
echo " 2. Spring Cloud"
echo ""
read -r -p "请输入 1/2 [1]: " DEPLOY_MODE
DEPLOY_MODE="${DEPLOY_MODE:-1}"
case "$DEPLOY_MODE" in
1)
COMPOSE_FILE="docker-compose.yml"
MODE_NAME="单体"
;;
2)
COMPOSE_FILE="docker-compose.cloud.yml"
MODE_NAME="Spring Cloud"
DEPLOY_MODE=3
;;
*)
echo "❌ 无效选择,请输入 1/2"
exit 1
;;
esac
echo ""
echo ">>> 已选择: ${MODE_NAME}"
echo ""
# === 应用配置DB_NAME 需在 MySQL 测试前获取) ===
echo "--- 应用配置 ---"
read -r -p "应用名称 [ghb]: " APP_NAME
APP_NAME="${APP_NAME:-test}"
read -r -p "应用端口 [8008]: " APP_PORT
APP_PORT="${APP_PORT:-8008}"
read -r -p "数据库名 [ghb_base]: " DB_NAME
DB_NAME="${DB_NAME:-ghb_base}"
JWT_SECRET=$(gen_jwt_secret)
echo ""
# === 二级选择MySQL ===
echo "--- MySQL ---"
echo " 1. Docker 一起安装"
echo " 2. 连接外部服务"
echo ""
read -r -p "请选择 1/2 [1]: " MYSQL_MODE_CHOICE
MYSQL_MODE_CHOICE="${MYSQL_MODE_CHOICE:-1}"
case "$MYSQL_MODE_CHOICE" in
1)
MYSQL_MODE="docker"
read -r -p "MySQL 端口 [3306]: " MYSQL_PORT
MYSQL_PORT="${MYSQL_PORT:-3306}"
read -r -s -p "MySQL root 密码 [ChangeMe123]: " MYSQL_ROOT_PASSWORD
echo ""
MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-ChangeMe123}"
MYSQL_HOST="mysql"
MYSQL_USER="root"
MYSQL_PASS="${MYSQL_ROOT_PASSWORD}"
;;
2)
MYSQL_MODE="external"
echo "请输入外部 MySQL 连接信息:"
read -r -p " 主机地址 [127.0.0.1]: " MYSQL_HOST
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
read -r -p " 端口 [3306]: " MYSQL_PORT
MYSQL_PORT="${MYSQL_PORT:-3306}"
read -r -p " 用户名 [root]: " MYSQL_USER
MYSQL_USER="${MYSQL_USER:-root}"
read -r -s -p " 密码: " MYSQL_PASS
echo ""
echo ""
echo ">>> 测试 MySQL 连接..."
check_mysql_connect_and_db "${MYSQL_HOST}" "${MYSQL_PORT}" "${MYSQL_USER}" "${MYSQL_PASS}" "${DB_NAME}"
local mysql_test_rc=$?
if [ $mysql_test_rc -eq 2 ]; then
# 连接成功但库不存在
echo ""
echo "⚠️ MySQL 连接成功,但数据库 '${DB_NAME}' 不存在!"
echo " 请先创建数据库:"
echo " CREATE DATABASE ${DB_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
echo ""
read -r -p "是否仍然继续?[y/N]: " continue_anyway
if [ "${continue_anyway}" != "y" ] && [ "${continue_anyway}" != "Y" ]; then
echo "❌ 已取消"
exit 1
fi
elif [ $mysql_test_rc -ne 0 ]; then
echo ""
echo "⚠️ MySQL 连接失败!请检查:"
echo " 1. MySQL 服务是否已启动"
echo " 2. 主机/端口/用户名/密码是否正确"
echo " 3. 防火墙是否放行 ${MYSQL_PORT} 端口"
echo ""
read -r -p "是否仍然继续?[y/N]: " continue_anyway
if [ "${continue_anyway}" != "y" ] && [ "${continue_anyway}" != "Y" ]; then
echo "❌ 已取消"
exit 1
fi
fi
;;
*)
echo "❌ 无效选择"
exit 1
;;
esac
echo ""
# === 二级选择Redis ===
echo "--- Redis ---"
echo " 1. Docker 一起安装"
echo " 2. 连接外部服务"
echo ""
read -r -p "请选择 1/2 [1]: " REDIS_MODE_CHOICE
REDIS_MODE_CHOICE="${REDIS_MODE_CHOICE:-1}"
case "$REDIS_MODE_CHOICE" in
1)
REDIS_MODE="docker"
read -r -p "Redis 端口 [6379]: " REDIS_HOST_PORT
REDIS_HOST_PORT="${REDIS_HOST_PORT:-6379}"
read -r -s -p "Redis 密码(无密码直接回车): " REDIS_PASS
echo ""
REDIS_HOST="redis"
REDIS_PORT="6379" # 容器内部固定 6379App 用此变量连接
REDIS_DB="0"
;;
2)
REDIS_MODE="external"
echo "请输入外部 Redis 连接信息:"
read -r -p " 主机地址 [127.0.0.1]: " REDIS_HOST
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
read -r -p " 端口 [6379]: " REDIS_PORT
REDIS_PORT="${REDIS_PORT:-6379}"
read -r -s -p " 密码(无密码直接回车): " REDIS_PASS
echo ""
read -r -p " DB 编号 [0]: " REDIS_DB
REDIS_DB="${REDIS_DB:-0}"
echo ""
echo ">>> 测试 Redis 连接..."
check_redis_connect "${REDIS_HOST}" "${REDIS_PORT}" "${REDIS_PASS}" || {
echo ""
echo "⚠️ Redis 连接失败!请检查:"
echo " 1. Redis 服务是否已启动"
echo " 2. 主机/端口/密码是否正确"
echo ""
read -r -p "是否仍然继续?[y/N]: " continue_anyway
if [ "${continue_anyway}" != "y" ] && [ "${continue_anyway}" != "Y" ]; then
echo "❌ 已取消"
exit 1
fi
}
;;
*)
echo "❌ 无效选择"
exit 1
;;
esac
echo ""
# === 二级选择MinIO ===
echo "--- MinIO ---"
echo " 1. Docker 一起安装"
echo " 2. 连接外部服务"
echo " 3. 不使用"
echo ""
read -r -p "请选择 1/2/3 [3]: " MINIO_MODE_CHOICE
MINIO_MODE_CHOICE="${MINIO_MODE_CHOICE:-3}"
case "$MINIO_MODE_CHOICE" in
1)
MINIO_MODE="docker"
MINIO_HOST="minio"
read -r -p "MinIO API 端口 [9000]: " MINIO_PORT
MINIO_PORT="${MINIO_PORT:-9000}"
read -r -p "MinIO Console 端口 [9001]: " MINIO_CONSOLE_PORT
MINIO_CONSOLE_PORT="${MINIO_CONSOLE_PORT:-9001}"
read -r -p "MinIO Access Key [minioadmin]: " MINIO_ACCESS_KEY
MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-minioadmin}"
while [ "${#MINIO_ACCESS_KEY}" -lt 3 ]; do
echo "❌ MinIO Access Key 长度至少为 3 位"
read -r -p "MinIO Access Key [minioadmin]: " MINIO_ACCESS_KEY
MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-minioadmin}"
done
read -r -s -p "MinIO Secret Key [minioadmin]: " MINIO_SECRET_KEY
echo ""
MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-minioadmin}"
while [ "${#MINIO_SECRET_KEY}" -lt 8 ]; do
echo "❌ MinIO Secret Key 长度至少为 8 位"
read -r -s -p "MinIO Secret Key: " MINIO_SECRET_KEY
echo ""
MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-minioadmin}"
done
read -r -p "MinIO Bucket 名称 [ghb-base]: " MINIO_BUCKET
MINIO_BUCKET="${MINIO_BUCKET:-ghb-base}"
MINIO_ENDPOINT="http://minio:9000"
;;
2)
MINIO_MODE="external"
echo "请输入外部 MinIO 连接信息:"
read -r -p " 主机地址 [127.0.0.1]: " MINIO_HOST
MINIO_HOST="${MINIO_HOST:-127.0.0.1}"
read -r -p " API 端口 [9000]: " MINIO_PORT
MINIO_PORT="${MINIO_PORT:-9000}"
read -r -p " Access Key [minioadmin]: " MINIO_ACCESS_KEY
MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-minioadmin}"
read -r -s -p " Secret Key [minioadmin]: " MINIO_SECRET_KEY
echo ""
MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-minioadmin}"
read -r -p " Bucket 名称 [ghb-base]: " MINIO_BUCKET
MINIO_BUCKET="${MINIO_BUCKET:-ghb-base}"
MINIO_ENDPOINT="http://${MINIO_HOST}:${MINIO_PORT}"
echo ""
echo ">>> 测试 MinIO 连接..."
check_minio_connect "${MINIO_HOST}" "${MINIO_PORT}" "${MINIO_ACCESS_KEY}" "${MINIO_SECRET_KEY}" || {
echo ""
echo "⚠️ MinIO 不可达!请检查:"
echo " 1. MinIO 服务是否已启动"
echo " 2. 主机/端口是否正确"
echo ""
read -r -p "是否仍然继续?[y/N]: " continue_anyway
if [ "${continue_anyway}" != "y" ] && [ "${continue_anyway}" != "Y" ]; then
echo "❌ 已取消"
exit 1
fi
}
;;
3)
MINIO_MODE="none"
MINIO_HOST="127.0.0.1"
MINIO_PORT="9000"
MINIO_ACCESS_KEY="minioadmin"
MINIO_SECRET_KEY="minioadmin"
MINIO_BUCKET="ghb-base"
MINIO_ENDPOINT="http://127.0.0.1:9000"
;;
*)
echo "❌ 无效选择"
exit 1
;;
esac
echo ""
# 自动拼接 JDBC URL
if [ "$MYSQL_MODE" = "docker" ]; then
MYSQL_URL="jdbc:mysql://mysql:3306/${DB_NAME}?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"
else
MYSQL_URL="jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"
fi
# 网络配置
echo ""
echo "--- 网络配置 ---"
read -r -p "Docker 内部网络名 [ghb-net]: " NETWORK_NAME
NETWORK_NAME="${NETWORK_NAME:-ghb-net}"
# SpringCloud 额外配置
if [ "$DEPLOY_MODE" = "3" ]; then
echo ""
echo "--- Spring Cloud 额外配置 ---"
read -r -p "Nacos 端口 [8848]: " NACOS_PORT
NACOS_PORT="${NACOS_PORT:-8848}"
read -r -p "Gateway 端口 [9999]: " GATEWAY_PORT
GATEWAY_PORT="${GATEWAY_PORT:-9999}"
read -r -p "System 服务端口 [7001]: " SYSTEM_PORT
SYSTEM_PORT="${SYSTEM_PORT:-7001}"
fi
# 数据库初始化external MySQL 时)
DB_INIT="false"
if [ "$MYSQL_MODE" = "external" ]; then
echo ""
read -r -p "外接数据库是否已建表?[Y/n]: " DB_READY
if [ "${DB_READY}" != "Y" ] && [ "${DB_READY}" != "y" ] && [ -n "${DB_READY}" ]; then
echo " 如果数据库是空的,可以自动执行建表脚本(${PROJECT_DIR}/db/ghb_base_init.sql"
read -r -p " 是否自动初始化数据库表?[y/N]: " DO_INIT_DB
if [ "${DO_INIT_DB}" = "y" ] || [ "${DO_INIT_DB}" = "Y" ]; then
DB_INIT="true"
fi
fi
fi
# 前端集成
FRONTEND_ENABLED="false"
echo ""
read -r -p "是否集成前端(需要先 cd ghb-frontend && pnpm build 生成 dist[y/N]: " ENABLE_FRONTEND
if [ "${ENABLE_FRONTEND}" = "y" ] || [ "${ENABLE_FRONTEND}" = "Y" ]; then
FRONTEND_ENABLED="true"
read -r -p "前端端口 [80]: " FRONTEND_PORT
FRONTEND_PORT="${FRONTEND_PORT:-80}"
fi
# === 生成 .env 文件 ===
echo ""
echo ">>> 正在生成 .env 文件..."
cat > "${DIR}/.env" << EOF
# ============================================
# ghb-base 部署配置
# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
# 部署架构: ${MODE_NAME}
# ============================================
DEPLOY_MODE=${DEPLOY_MODE}
COMPOSE_FILE=${COMPOSE_FILE}
# 应用
APP_NAME=${APP_NAME}
APP_PORT=${APP_PORT}
JAVA_OPTS="-Xms512m -Xmx512m"
# 数据库
DB_NAME=${DB_NAME}
# 网络
NETWORK_NAME=${NETWORK_NAME}
# ---- 中间件策略 ----
# docker = Docker 一起安装 | external = 连接外部服务 | none = 不使用(仅 MinIO
MYSQL_MODE=${MYSQL_MODE}
REDIS_MODE=${REDIS_MODE}
MINIO_MODE=${MINIO_MODE}
EOF
# MySQL 配置
if [ "$MYSQL_MODE" = "docker" ]; then
cat >> "${DIR}/.env" << EOF
# ---- MySQLDocker ----
MYSQL_PORT=${MYSQL_PORT}
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
MYSQL_HOST=${MYSQL_HOST}
MYSQL_USER=${MYSQL_USER}
MYSQL_PASS='${MYSQL_PASS}'
MYSQL_URL='${MYSQL_URL}'
EOF
else
cat >> "${DIR}/.env" << EOF
# ---- MySQL外部 ----
MYSQL_HOST=${MYSQL_HOST}
MYSQL_PORT=${MYSQL_PORT}
MYSQL_USER=${MYSQL_USER}
MYSQL_PASS='${MYSQL_PASS}'
MYSQL_URL='${MYSQL_URL}'
EOF
fi
# Redis 配置
if [ "$REDIS_MODE" = "docker" ]; then
cat >> "${DIR}/.env" << EOF
# ---- RedisDocker ----
REDIS_PORT=${REDIS_PORT}
REDIS_HOST_PORT=${REDIS_HOST_PORT}
REDIS_PASS='${REDIS_PASS}'
REDIS_HOST=${REDIS_HOST}
REDIS_DB=${REDIS_DB}
EOF
else
cat >> "${DIR}/.env" << EOF
# ---- Redis外部 ----
REDIS_HOST=${REDIS_HOST}
REDIS_PORT=${REDIS_PORT}
REDIS_PASS='${REDIS_PASS}'
REDIS_DB=${REDIS_DB}
EOF
fi
# MinIO 配置
if [ "$MINIO_MODE" = "docker" ]; then
cat >> "${DIR}/.env" << EOF
# ---- MinIODocker ----
MINIO_HOST=${MINIO_HOST}
MINIO_PORT=${MINIO_PORT}
MINIO_CONSOLE_PORT=${MINIO_CONSOLE_PORT}
MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
MINIO_BUCKET=${MINIO_BUCKET}
MINIO_ENDPOINT=${MINIO_ENDPOINT}
EOF
elif [ "$MINIO_MODE" = "external" ]; then
cat >> "${DIR}/.env" << EOF
# ---- MinIO外部 ----
MINIO_HOST=${MINIO_HOST}
MINIO_PORT=${MINIO_PORT}
MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
MINIO_BUCKET=${MINIO_BUCKET}
MINIO_ENDPOINT=${MINIO_ENDPOINT}
EOF
else
cat >> "${DIR}/.env" << EOF
# ---- MinIO不使用 ----
MINIO_HOST=127.0.0.1
MINIO_PORT=9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=ghb-base
MINIO_ENDPOINT=http://127.0.0.1:9000
EOF
fi
# SpringCloud 额外配置
if [ "$DEPLOY_MODE" = "3" ]; then
cat >> "${DIR}/.env" << EOF
# ---- Spring Cloud ----
NACOS_PORT=${NACOS_PORT}
GATEWAY_PORT=${GATEWAY_PORT}
SYSTEM_PORT=${SYSTEM_PORT}
EOF
fi
# 数据库初始化
cat >> "${DIR}/.env" << EOF
# 数据库初始化true 时 start/rebuild 会自动建表)
DB_INIT=${DB_INIT}
EOF
# JWT
cat >> "${DIR}/.env" << EOF
# JWT
JWT_SECRET='${JWT_SECRET}'
EOF
# 前端
cat >> "${DIR}/.env" << EOF
# 前端集成
FRONTEND_ENABLED=${FRONTEND_ENABLED}
EOF
if [ "${FRONTEND_ENABLED}" = "true" ]; then
echo "FRONTEND_PORT=${FRONTEND_PORT}" >> "${DIR}/.env"
fi
# 上传方式
cat >> "${DIR}/.env" << EOF
# 上传方式: local(本地) / minio(MinIO)
JEECG_UPLOAD_TYPE=local
# 跳过连通性检查true 时不检查外接服务是否可达)
SKIP_CONNECTIVITY_CHECK=false
EOF
# === 输出摘要 ===
echo ""
echo "========================================="
echo " ✅ .env 文件已生成"
echo "========================================="
echo ""
echo "部署架构: ${MODE_NAME}"
echo "MySQL: ${MYSQL_MODE}"
echo "Redis: ${REDIS_MODE}"
echo "MinIO: ${MINIO_MODE}"
if [ "${FRONTEND_ENABLED}" = "true" ]; then
echo "前端: 已集成(端口 ${FRONTEND_PORT:-80}"
echo " 注意:需要先 cd ghb-frontend && pnpm build 生成 dist/"
fi
echo ""
echo "下一步:"
echo " bash deploy.sh build # 编译镜像"
echo " bash deploy.sh start # 启动服务"
if [ "$DEPLOY_MODE" = "3" ]; then
echo " bash deploy.sh init-nacos # 初始化 Nacos 配置"
fi
echo ""
}
# ============================================
# start — 启动
# ============================================
do_start() {
load_env
local COMPOSE_CMD
COMPOSE_CMD=$(get_compose_cmd)
local COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
echo ">>> 部署架构: ${DEPLOY_MODE:-1}"
echo ">>> MySQL: ${MYSQL_MODE:-docker} Redis: ${REDIS_MODE:-docker} MinIO: ${MINIO_MODE:-none}"
# 检查 external 服务连通性
if [ "${SKIP_CONNECTIVITY_CHECK:-false}" != "true" ]; then
check_connectivity || {
echo ""
echo "⚠️ 部分外接服务不可达,部署可能失败。"
echo ""
echo " 请选择:"
echo " 1. 退出,先修复连接问题"
echo " 2. 强制部署(跳过检查)"
echo ""
read -r -p " 请输入 1/2 [1]: " choice
case "${choice:-1}" in
2) echo ">>> 跳过检查,强制部署..." ;;
*) echo "❌ 已取消"; exit 1 ;;
esac
}
fi
echo ">>> 停掉旧容器..."
$COMPOSE_CMD down 2>/dev/null || true
echo ""
if [ "${DEPLOY_MODE}" = "3" ]; then
# ============================================
# Cloud 模式:分阶段启动
# Phase 1: docker 中间件 → Nacos
# Phase 2: 推送配置 → 业务服务
# ============================================
echo ">>> [Phase 1] 启动中间件和 Nacos..."
# 按需启动 docker 中间件
if [ "${MYSQL_MODE:-docker}" = "docker" ]; then
echo " 启动 MySQL..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile mysql up -d --force-recreate mysql
wait_for_healthy "${APP_NAME:-test}-mysql" "MySQL" 60 || {
echo "❌ MySQL 启动失败"
exit 1
}
verify_mysql_db
fi
if [ "${REDIS_MODE:-docker}" = "docker" ]; then
echo " 启动 Redis..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile redis up -d --force-recreate redis
wait_for_healthy "${APP_NAME:-test}-redis" "Redis" 30 || {
echo "❌ Redis 启动失败"
exit 1
}
verify_redis
fi
# 启动 Nacos
echo " 启动 Nacos..."
docker compose -f "${DIR}/${COMPOSE_FILE}" up -d --force-recreate nacos
# 等待 Nacos 健康
echo ">>> 等待 Nacos 就绪..."
local NACOS_HEALTH_URL="http://localhost:${NACOS_PORT:-8848}/nacos/v1/console/health/liveness"
for i in $(seq 1 60); do
if curl -s "$NACOS_HEALTH_URL" 2>/dev/null | grep -q "OK"; then
echo " ✅ Nacos 已就绪"
break
fi
if [ "$i" -eq 60 ]; then
echo " ❌ Nacos 启动超时,请检查日志: docker logs ${APP_NAME:-test}-nacos"
exit 1
fi
sleep 3
done
# 首次部署Nacos 无配置则自动导入
echo ">>> 检查 Nacos 配置..."
local CHECK_RESP
CHECK_RESP=$(curl -s "http://localhost:${NACOS_PORT:-8848}/nacos/v1/cs/configs?dataId=jeecg.yaml&group=DEFAULT_GROUP")
if [ -z "$CHECK_RESP" ] || [ "$CHECK_RESP" = "config data not exist" ]; then
echo " 首次部署,自动导入配置..."
NACOS_URL="http://localhost:${NACOS_PORT:-8848}" bash "${DIR}/init-nacos.sh"
else
echo " ✅ Nacos 配置已存在,跳过导入"
fi
# Phase 2: 启动业务服务
echo ""
echo ">>> [Phase 2] 启动业务服务..."
$COMPOSE_CMD up -d --force-recreate
echo ""
echo "✅ 已启动"
echo " Nacos: http://localhost:${NACOS_PORT:-8848}/nacos"
echo " Gateway: http://localhost:${GATEWAY_PORT:-9999}"
echo " System: http://localhost:${SYSTEM_PORT:-7001}"
if [ "${MINIO_MODE:-none}" = "docker" ]; then
echo " MinIO: http://localhost:${MINIO_PORT:-9000} (Console: ${MINIO_CONSOLE_PORT:-9001})"
fi
if [ "${FRONTEND_ENABLED:-false}" = "true" ]; then
echo " 前端: http://localhost:${FRONTEND_PORT:-80}"
fi
else
# ============================================
# 单体模式:按需分步启动
# ============================================
# 先启动 docker 中间件
if [ "${MYSQL_MODE:-docker}" = "docker" ]; then
echo ">>> 启动 MySQL..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile mysql up -d --force-recreate mysql
wait_for_healthy "${APP_NAME:-test}-mysql" "MySQL" 60 || {
echo "❌ MySQL 启动失败"
exit 1
}
verify_mysql_db
fi
if [ "${REDIS_MODE:-docker}" = "docker" ]; then
echo ">>> 启动 Redis..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile redis up -d --force-recreate redis
wait_for_healthy "${APP_NAME:-test}-redis" "Redis" 30 || {
echo "❌ Redis 启动失败"
exit 1
}
verify_redis
fi
if [ "${MINIO_MODE:-none}" = "docker" ]; then
echo ">>> 启动 MinIO..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile minio up -d --force-recreate minio
fi
# 启动 app及其它 profiled 服务如 frontend
echo ">>> 启动应用..."
$COMPOSE_CMD up -d --force-recreate
echo ""
echo "✅ 已启动"
echo " 地址: http://localhost:${APP_PORT}/ghb"
if [ "${MINIO_MODE:-none}" = "docker" ]; then
echo " MinIO: http://localhost:${MINIO_PORT:-9000} (Console: ${MINIO_CONSOLE_PORT:-9001})"
fi
if [ "${FRONTEND_ENABLED:-false}" = "true" ]; then
echo " 前端: http://localhost:${FRONTEND_PORT:-80}"
fi
fi
echo " 日志: bash deploy.sh logs"
}
# ============================================
# build — 编译镜像
# ============================================
do_build() {
load_env
local COMPOSE_CMD
COMPOSE_CMD=$(get_compose_cmd)
echo ">>> 部署架构: ${DEPLOY_MODE:-1}"
echo ">>> 编译镜像..."
if [ "${DEPLOY_MODE}" = "3" ]; then
echo " 编译所有 Cloud 服务(首次较慢,约 10-20 分钟)..."
$COMPOSE_CMD build
else
$COMPOSE_CMD build app
fi
echo ""
echo "✅ 编译完成,执行 bash deploy.sh start 启动"
}
# ============================================
# rebuild — 拉取代码 + 编译 + 启动
# ============================================
do_rebuild() {
load_env
local COMPOSE_CMD
COMPOSE_CMD=$(get_compose_cmd)
local COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
echo ">>> 部署架构: ${DEPLOY_MODE:-1}"
echo ">>> 切到分支 ${BRANCH} 并拉取最新代码..."
# 拉代码前检查 external 服务连通性
if [ "${SKIP_CONNECTIVITY_CHECK:-false}" != "true" ]; then
check_connectivity || {
echo ""
echo "⚠️ 部分外接服务不可达,部署可能失败。"
read -r -p " 是否强制继续?[y/N]: " force
if [ "${force}" != "y" ] && [ "${force}" != "Y" ]; then
echo "❌ 已取消"
exit 1
fi
echo ">>> 跳过检查,强制继续..."
}
fi
if [ "${DEPLOY_MODE}" = "3" ]; then
# Cloud 模式需要拉取完整仓库ghb-base 根目录)
local REPO_DIR="$(dirname "$PROJECT_DIR")"
cd "$REPO_DIR"
else
cd "$PROJECT_DIR"
fi
git fetch origin
git checkout "${BRANCH}"
git pull origin "${BRANCH}"
cd "$DIR"
echo ""
echo ">>> 重新编译..."
if [ "${DEPLOY_MODE}" = "3" ]; then
echo " 编译所有 Cloud 服务(首次较慢,约 10-20 分钟)..."
$COMPOSE_CMD build --no-cache
else
$COMPOSE_CMD build --no-cache app
fi
echo ""
if [ "${DEPLOY_MODE}" = "3" ]; then
# Cloud 模式:分阶段启动
echo ">>> [Phase 1] 启动中间件和 Nacos..."
$COMPOSE_CMD down 2>/dev/null || true
if [ "${MYSQL_MODE:-docker}" = "docker" ]; then
echo " 启动 MySQL..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile mysql up -d --force-recreate mysql
wait_for_healthy "${APP_NAME:-test}-mysql" "MySQL" 60 || {
echo "❌ MySQL 启动失败"
exit 1
}
verify_mysql_db
fi
if [ "${REDIS_MODE:-docker}" = "docker" ]; then
echo " 启动 Redis..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile redis up -d --force-recreate redis
wait_for_healthy "${APP_NAME:-test}-redis" "Redis" 30 || {
echo "❌ Redis 启动失败"
exit 1
}
verify_redis
fi
echo " 启动 Nacos..."
docker compose -f "${DIR}/${COMPOSE_FILE}" up -d --force-recreate nacos
echo ">>> 等待 Nacos 就绪..."
local NACOS_HEALTH_URL="http://localhost:${NACOS_PORT:-8848}/nacos/v1/console/health/liveness"
for i in $(seq 1 60); do
if curl -s "$NACOS_HEALTH_URL" 2>/dev/null | grep -q "OK"; then
echo " ✅ Nacos 已就绪"
break
fi
if [ "$i" -eq 60 ]; then
echo " ❌ Nacos 启动超时"
exit 1
fi
sleep 3
done
# 首次部署Nacos 无配置则自动导入
echo ">>> 检查 Nacos 配置..."
local CHECK_RESP
CHECK_RESP=$(curl -s "http://localhost:${NACOS_PORT:-8848}/nacos/v1/cs/configs?dataId=jeecg.yaml&group=DEFAULT_GROUP")
if [ -z "$CHECK_RESP" ] || [ "$CHECK_RESP" = "config data not exist" ]; then
echo " 首次部署,自动导入配置..."
NACOS_URL="http://localhost:${NACOS_PORT:-8848}" bash "${DIR}/init-nacos.sh"
else
echo " ✅ Nacos 配置已存在,跳过导入"
fi
echo ""
echo ">>> [Phase 2] 启动业务服务..."
$COMPOSE_CMD up -d --force-recreate
echo ""
echo "✅ 重新部署完成(分支: ${BRANCH}"
else
# 单体模式:按需分步启动
$COMPOSE_CMD down 2>/dev/null || true
if [ "${MYSQL_MODE:-docker}" = "docker" ]; then
echo ">>> 启动 MySQL..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile mysql up -d --force-recreate mysql
wait_for_healthy "${APP_NAME:-test}-mysql" "MySQL" 60 || {
echo "❌ MySQL 启动失败"
exit 1
}
verify_mysql_db
fi
if [ "${REDIS_MODE:-docker}" = "docker" ]; then
echo ">>> 启动 Redis..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile redis up -d --force-recreate redis
wait_for_healthy "${APP_NAME:-test}-redis" "Redis" 30 || {
echo "❌ Redis 启动失败"
exit 1
}
verify_redis
fi
if [ "${MINIO_MODE:-none}" = "docker" ]; then
echo ">>> 启动 MinIO..."
docker compose -f "${DIR}/${COMPOSE_FILE}" --profile minio up -d --force-recreate minio
fi
echo ">>> 启动应用..."
$COMPOSE_CMD up -d --force-recreate
echo ""
echo "✅ 重新部署完成(分支: ${BRANCH}"
fi
}
# ============================================
# stop — 停止
# ============================================
do_stop() {
load_env
local COMPOSE_CMD
COMPOSE_CMD=$(get_compose_cmd)
$COMPOSE_CMD stop 2>/dev/null || true
echo "✅ 已停止"
}
# ============================================
# logs — 查看日志
# ============================================
do_logs() {
load_env
local COMPOSE_CMD
COMPOSE_CMD=$(get_compose_cmd)
$COMPOSE_CMD logs -f --tail=200
}
# ============================================
# status — 容器状态
# ============================================
do_status() {
load_env
local COMPOSE_CMD
COMPOSE_CMD=$(get_compose_cmd)
$COMPOSE_CMD ps -a
}
# ============================================
# config — 检查配置
# ============================================
do_config() {
load_env
echo "========================================="
echo " 部署架构: ${DEPLOY_MODE:-1}$( [ "${DEPLOY_MODE:-1}" = "3" ] && echo "Spring Cloud" || echo "单体" )"
echo " Compose 文件: ${COMPOSE_FILE:-docker-compose.yml}"
echo " MySQL: ${MYSQL_MODE:-docker}"
echo " Redis: ${REDIS_MODE:-docker}"
echo " MinIO: ${MINIO_MODE:-none}"
echo " 网络: ${NETWORK_NAME:-ghb-net}"
echo "========================================="
echo ""
echo "========================================="
echo " .env 文件内容(敏感信息已脱敏)"
echo "========================================="
grep -v '^#' "${DIR}/.env" | grep -v '^$' | sed 's/\(PASS=\).*/\1***/' | sed 's/\(PWD=\).*/\1***/' | sed 's/\(PASSWORD=\).*/\1***/' | sed 's/\(SECRET=\).*/\1***/' | sed 's/\(_KEY=\).*/\1***/'
echo ""
echo "========================================="
echo " Docker 解析后的环境变量"
echo "========================================="
local COMPOSE_CMD
COMPOSE_CMD=$(get_compose_cmd)
$COMPOSE_CMD config 2>&1 | grep -E 'MYSQL_|REDIS_|JWT_|SPRING_|APP_|JAVA|MYSQL-|NACOS|GATEWAY|DB_|MINIO_|NETWORK_' | sed 's/\(PASS: \).*/\1***/' | sed 's/\(PWD: \).*/\1***/' | sed 's/\(SECRET: \).*/\1***/' | sed 's/\(_KEY: \).*/\1***/'
echo ""
if [ "${DEPLOY_MODE}" != "3" ]; then
echo "========================================="
echo " application-prod.yml 内容"
echo "========================================="
cat "${DIR}/config/application-prod.yml"
echo ""
fi
if [ "${DEPLOY_MODE}" = "3" ]; then
echo "========================================="
echo " Nacos 配置文件列表(部署后执行 init-nacos 导入)"
echo "========================================="
ls -la "${DIR}/nacos/config/" 2>/dev/null || echo " (无)"
echo ""
fi
}
# ============================================
# init-nacos — 导入 Nacos 配置(仅 Cloud 模式)
# ============================================
do_init_nacos() {
load_env
if [ "${DEPLOY_MODE}" != "3" ]; then
echo "❌ 当前部署架构为 ${DEPLOY_MODE:-1}Nacos 配置仅适用于 Spring Cloud 模式"
exit 1
fi
if [ ! -f "${DIR}/init-nacos.sh" ]; then
echo "❌ 未找到 init-nacos.sh"
exit 1
fi
NACOS_URL="http://localhost:${NACOS_PORT:-8848}" bash "${DIR}/init-nacos.sh" "$@"
}
# ============================================
# help
# ============================================
do_help() {
echo "用法: bash deploy.sh <命令> [分支]"
echo ""
echo "命令:"
echo " init 交互式初始化 .env 配置文件"
echo " start 停旧容器 → 启动(日常使用,不重新编译)"
echo " build [分支] 编译镜像(代码变更后用一次)"
echo " rebuild [分支] 拉取分支代码 + 编译 + 启动(一步到位)"
echo " stop 停止所有容器"
echo " logs 查看日志"
echo " status 查看容器状态"
echo " config 检查配置(不启动)"
echo " init-db 初始化外接数据库表(仅 external MySQL"
echo " init-nacos 初始化 Nacos 配置(仅 Spring Cloud 模式)"
echo ""
echo "部署架构:"
echo " 1. 单体 → docker-compose.yml"
echo " 3. Spring Cloud → docker-compose.cloud.yml"
echo ""
echo "中间件策略init 时独立选择):"
echo " MySQL: dockerDocker 安装)/ external连接外部"
echo " Redis: dockerDocker 安装)/ external连接外部"
echo " MinIO: dockerDocker 安装)/ external连接外部/ none不使用"
echo ""
echo "示例:"
echo " bash deploy.sh init # 首次:交互式配置"
echo " bash deploy.sh build # 编译镜像"
echo " bash deploy.sh start # 启动"
echo " bash deploy.sh rebuild test # 拉取 test 分支 + 编译 + 启动"
echo " bash deploy.sh logs # 查看日志"
}
# ============================================
# 路由
# ============================================
case "$CMD" in
init) do_init ;;
start) do_start ;;
build) do_build ;;
rebuild) do_rebuild ;;
stop) do_stop ;;
logs) do_logs ;;
status) do_status ;;
config) do_config ;;
init-nacos) do_init_nacos ;;
init-db) do_init_db ;;
help|--help|-h) do_help ;;
*)
do_help
;;
esac