d07c4c2c5e
- Add admin reset password key to deployment completion info - Fix color code display by adding -e flag to echo commands - Display admin reset key in plaintext for easy reference
511 lines
17 KiB
Bash
Executable File
511 lines
17 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# ========================================
|
||
# PolyHermes 交互式一键部署脚本
|
||
# ========================================
|
||
# 功能:
|
||
# - 交互式配置环境变量
|
||
# - 自动生成安全密钥
|
||
# - 使用 Docker Hub 线上镜像部署
|
||
# - 支持配置预检和回滚
|
||
# ========================================
|
||
|
||
set -e
|
||
|
||
# 颜色输出
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# 打印函数
|
||
info() {
|
||
echo -e "${GREEN}[✓]${NC} $1"
|
||
}
|
||
|
||
warn() {
|
||
echo -e "${YELLOW}[⚠]${NC} $1"
|
||
}
|
||
|
||
error() {
|
||
echo -e "${RED}[✗]${NC} $1"
|
||
}
|
||
|
||
title() {
|
||
echo -e "${CYAN}${1}${NC}"
|
||
}
|
||
|
||
# 生成随机密钥
|
||
generate_secret() {
|
||
local length=${1:-32}
|
||
if command -v openssl &> /dev/null; then
|
||
openssl rand -hex $length
|
||
else
|
||
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $((length * 2)) | head -n 1
|
||
fi
|
||
}
|
||
|
||
# 生成随机端口号(10000-60000之间)
|
||
generate_random_port() {
|
||
echo $((10000 + RANDOM % 50001))
|
||
}
|
||
|
||
# 读取用户输入(支持默认值)
|
||
read_input() {
|
||
local prompt="$1"
|
||
local default="$2"
|
||
local is_secret="$3"
|
||
local value=""
|
||
|
||
# 构建提示信息(不使用颜色,因为 read -p 可能不支持)
|
||
local prompt_text=""
|
||
if [ -n "$default" ]; then
|
||
if [ "$is_secret" = "secret" ]; then
|
||
prompt_text="${prompt} [回车自动生成]: "
|
||
else
|
||
prompt_text="${prompt} [默认: ${default}]: "
|
||
fi
|
||
else
|
||
prompt_text="${prompt}: "
|
||
fi
|
||
|
||
# 使用 read -p 确保提示正确显示
|
||
read -r -p "$prompt_text" value
|
||
|
||
# 如果用户没有输入,使用默认值
|
||
if [ -z "$value" ]; then
|
||
if [ "$is_secret" = "secret" ] && [ -z "$default" ]; then
|
||
# 自动生成密钥
|
||
case "$prompt" in
|
||
*JWT*)
|
||
value=$(generate_secret 64)
|
||
# 输出到 stderr,避免被捕获到返回值中
|
||
info "已自动生成 JWT 密钥(128字符)" >&2
|
||
;;
|
||
*管理员*|*ADMIN*)
|
||
value=$(generate_secret 32)
|
||
info "已自动生成管理员重置密钥(64字符)" >&2
|
||
;;
|
||
*加密*|*CRYPTO*)
|
||
value=$(generate_secret 32)
|
||
info "已自动生成加密密钥(64字符)" >&2
|
||
;;
|
||
*数据库密码*|*DB_PASSWORD*)
|
||
value=$(generate_secret 16)
|
||
info "已自动生成数据库密码(32字符)" >&2
|
||
;;
|
||
*)
|
||
value="$default"
|
||
;;
|
||
esac
|
||
else
|
||
value="$default"
|
||
fi
|
||
fi
|
||
|
||
echo "$value"
|
||
}
|
||
|
||
# 检查 Docker 环境
|
||
check_docker() {
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
title " 步骤 1: 环境检查"
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
# 检查 Docker
|
||
if ! command -v docker &> /dev/null; then
|
||
error "Docker 未安装"
|
||
echo ""
|
||
info "请先安装 Docker:"
|
||
info " macOS: brew install docker"
|
||
info " Ubuntu/Debian: apt-get install docker.io"
|
||
info " CentOS/RHEL: yum install docker"
|
||
exit 1
|
||
fi
|
||
info "Docker 已安装: $(docker --version | head -1)"
|
||
|
||
# 检查 Docker Compose
|
||
if docker compose version &> /dev/null 2>&1; then
|
||
info "Docker Compose 已安装: $(docker compose version)"
|
||
elif command -v docker-compose &> /dev/null; then
|
||
info "Docker Compose 已安装: $(docker-compose --version)"
|
||
else
|
||
error "Docker Compose 未安装"
|
||
echo ""
|
||
info "请先安装 Docker Compose:"
|
||
info " https://docs.docker.com/compose/install/"
|
||
exit 1
|
||
fi
|
||
|
||
# 检查 Docker 守护进程
|
||
if ! docker info &> /dev/null; then
|
||
error "Docker 守护进程未运行"
|
||
info "请启动 Docker 服务:"
|
||
info " macOS: 打开 Docker Desktop"
|
||
info " Linux: systemctl start docker"
|
||
exit 1
|
||
fi
|
||
info "Docker 守护进程运行正常"
|
||
|
||
echo ""
|
||
}
|
||
|
||
# 交互式配置收集
|
||
collect_configuration() {
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
title " 步骤 2: 配置收集"
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
info "💡 所有配置项均为可选,直接按回车即可使用默认值或自动生成"
|
||
echo ""
|
||
warn "密钥配置:回车将自动生成安全的随机密钥"
|
||
warn "其他配置:回车将使用括号中的默认值"
|
||
echo ""
|
||
|
||
# 基础配置
|
||
title "【基础配置】"
|
||
echo -e "${CYAN}将配置:服务器端口、MySQL端口、时区${NC}"
|
||
# 生成随机端口作为默认值
|
||
DEFAULT_PORT=$(generate_random_port)
|
||
SERVER_PORT=$(read_input "➤ 服务器端口" "$DEFAULT_PORT")
|
||
MYSQL_PORT=$(read_input "➤ MySQL 端口(外部访问)" "3307")
|
||
TZ=$(read_input "➤ 时区" "Asia/Shanghai")
|
||
echo ""
|
||
|
||
# 数据库配置
|
||
title "【数据库配置】"
|
||
echo -e "${CYAN}将配置:数据库用户名、数据库密码${NC}"
|
||
echo -e "${YELLOW}💡 提示:密码留空将自动生成 32 字符的安全随机密码${NC}"
|
||
DB_USERNAME=$(read_input "➤ 数据库用户名" "root")
|
||
DB_PASSWORD=$(read_input "➤ 数据库密码" "" "secret")
|
||
echo ""
|
||
|
||
# 安全配置
|
||
title "【安全配置】"
|
||
echo -e "${CYAN}将配置:JWT密钥、管理员密码重置密钥、数据加密密钥${NC}"
|
||
echo -e "${YELLOW}💡 提示:留空将自动生成高强度随机密钥(推荐)${NC}"
|
||
JWT_SECRET=$(read_input "➤ JWT 密钥" "" "secret")
|
||
ADMIN_RESET_PASSWORD_KEY=$(read_input "➤ 管理员密码重置密钥" "" "secret")
|
||
CRYPTO_SECRET_KEY=$(read_input "➤ 加密密钥(用于加密 API Key)" "" "secret")
|
||
echo ""
|
||
|
||
# 日志配置
|
||
title "【日志配置】"
|
||
echo -e "${CYAN}将配置:Root日志级别、应用日志级别${NC}"
|
||
echo -e "${YELLOW}可选级别: TRACE, DEBUG, INFO, WARN, ERROR, OFF${NC}"
|
||
LOG_LEVEL_ROOT=$(read_input "➤ Root 日志级别(第三方库)" "WARN")
|
||
LOG_LEVEL_APP=$(read_input "➤ 应用日志级别" "INFO")
|
||
echo ""
|
||
|
||
# 自动设置不需要用户输入的配置
|
||
SPRING_PROFILES_ACTIVE="prod"
|
||
ALLOW_PRERELEASE="false"
|
||
GITHUB_REPO="WrBug/PolyHermes"
|
||
}
|
||
|
||
# 下载 docker-compose.prod.yml(如果不存在)
|
||
download_docker_compose_file() {
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
title " 步骤 3: 获取部署配置"
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
if [ -f "docker-compose.prod.yml" ]; then
|
||
info "检测到现有 docker-compose.prod.yml,跳过下载"
|
||
echo ""
|
||
return 0
|
||
fi
|
||
|
||
info "正在从 GitHub 下载 docker-compose.prod.yml..."
|
||
|
||
# GitHub raw 文件链接
|
||
local compose_url="https://raw.githubusercontent.com/WrBug/PolyHermes/main/docker-compose.prod.yml"
|
||
|
||
# 尝试下载
|
||
if curl -fsSL "$compose_url" -o docker-compose.prod.yml; then
|
||
info "docker-compose.prod.yml 下载成功"
|
||
else
|
||
error "docker-compose.prod.yml 下载失败"
|
||
warn "请检查网络连接或手动下载:"
|
||
warn " $compose_url"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
}
|
||
|
||
# 生成 .env 文件
|
||
generate_env_file() {
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
title " 步骤 4: 生成环境变量文件"
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
# 备份现有 .env 文件
|
||
if [ -f ".env" ]; then
|
||
BACKUP_FILE=".env.backup.$(date +%Y%m%d_%H%M%S)"
|
||
cp .env "$BACKUP_FILE"
|
||
warn "已备份现有配置文件到: $BACKUP_FILE"
|
||
fi
|
||
|
||
# 生成新的 .env 文件
|
||
cat > .env <<EOF
|
||
# ========================================
|
||
# PolyHermes 生产环境配置
|
||
# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
|
||
# ========================================
|
||
|
||
# ============================================
|
||
# 基础配置
|
||
# ============================================
|
||
TZ=${TZ}
|
||
SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE}
|
||
SERVER_PORT=${SERVER_PORT}
|
||
MYSQL_PORT=${MYSQL_PORT}
|
||
|
||
# ============================================
|
||
# 数据库配置
|
||
# ============================================
|
||
DB_URL=jdbc:mysql://mysql:3306/polyhermes?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&allowPublicKeyRetrieval=true
|
||
DB_USERNAME=${DB_USERNAME}
|
||
DB_PASSWORD=${DB_PASSWORD}
|
||
|
||
# ============================================
|
||
# 安全配置(请妥善保管)
|
||
# ============================================
|
||
JWT_SECRET=${JWT_SECRET}
|
||
ADMIN_RESET_PASSWORD_KEY=${ADMIN_RESET_PASSWORD_KEY}
|
||
CRYPTO_SECRET_KEY=${CRYPTO_SECRET_KEY}
|
||
|
||
# ============================================
|
||
# 日志配置
|
||
# ============================================
|
||
LOG_LEVEL_ROOT=${LOG_LEVEL_ROOT}
|
||
LOG_LEVEL_APP=${LOG_LEVEL_APP}
|
||
|
||
# ============================================
|
||
# 其他配置
|
||
# ============================================
|
||
ALLOW_PRERELEASE=${ALLOW_PRERELEASE}
|
||
GITHUB_REPO=${GITHUB_REPO}
|
||
EOF
|
||
|
||
info "配置文件已生成: .env"
|
||
echo ""
|
||
|
||
# 显示配置摘要
|
||
title "【配置摘要】"
|
||
echo " 服务器端口: ${SERVER_PORT}"
|
||
echo " MySQL 端口: ${MYSQL_PORT}"
|
||
echo " 时区: ${TZ}"
|
||
echo " 数据库用户: ${DB_USERNAME}"
|
||
echo " 数据库密码: ${DB_PASSWORD:0:8}... (已隐藏)"
|
||
echo " JWT 密钥: ${JWT_SECRET:0:16}... (已隐藏)"
|
||
echo " 管理员重置密钥: ${ADMIN_RESET_PASSWORD_KEY:0:16}... (已隐藏)"
|
||
echo " 加密密钥: ${CRYPTO_SECRET_KEY:0:16}... (已隐藏)"
|
||
echo " 日志级别: Root=${LOG_LEVEL_ROOT}, App=${LOG_LEVEL_APP}"
|
||
echo ""
|
||
}
|
||
|
||
# 拉取镜像
|
||
pull_images() {
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
title " 步骤 5: 拉取 Docker 镜像"
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
info "正在从 Docker Hub 拉取最新镜像..."
|
||
|
||
# 拉取应用镜像
|
||
if docker pull wrbug/polyhermes:latest; then
|
||
info "应用镜像拉取成功: wrbug/polyhermes:latest"
|
||
else
|
||
error "应用镜像拉取失败"
|
||
warn "可能的原因:"
|
||
warn " 1. 网络连接问题"
|
||
warn " 2. Docker Hub 服务异常"
|
||
warn " 3. 镜像不存在"
|
||
exit 1
|
||
fi
|
||
|
||
# 拉取 MySQL 镜像
|
||
if docker pull mysql:8.2; then
|
||
info "MySQL 镜像拉取成功: mysql:8.2"
|
||
else
|
||
warn "MySQL 镜像拉取失败,将在启动时自动下载"
|
||
fi
|
||
|
||
echo ""
|
||
}
|
||
|
||
# 部署服务
|
||
deploy_services() {
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
title " 步骤 6: 部署服务"
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
# 停止现有服务
|
||
if docker compose -f docker-compose.prod.yml ps -q 2>/dev/null | grep -q .; then
|
||
warn "检测到正在运行的服务,正在停止..."
|
||
docker compose -f docker-compose.prod.yml down
|
||
info "已停止现有服务"
|
||
fi
|
||
|
||
# 启动服务
|
||
info "正在启动服务..."
|
||
if docker compose -f docker-compose.prod.yml up -d; then
|
||
info "服务启动成功"
|
||
else
|
||
error "服务启动失败"
|
||
error "请检查日志: docker compose -f docker-compose.prod.yml logs"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
}
|
||
|
||
# 健康检查
|
||
health_check() {
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
title " 步骤 7: 健康检查"
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
info "等待服务启动(最多等待 60 秒)..."
|
||
|
||
local max_attempts=12
|
||
local attempt=0
|
||
|
||
while [ $attempt -lt $max_attempts ]; do
|
||
attempt=$((attempt + 1))
|
||
|
||
# 检查容器状态
|
||
if docker compose -f docker-compose.prod.yml ps | grep -q "Up"; then
|
||
info "容器运行正常"
|
||
|
||
# 检查应用是否响应
|
||
if curl -s -o /dev/null -w "%{http_code}" http://localhost:${SERVER_PORT} | grep -q "200\|302\|401"; then
|
||
info "应用响应正常"
|
||
echo ""
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
echo -n "."
|
||
sleep 5
|
||
done
|
||
|
||
echo ""
|
||
warn "健康检查超时,请手动检查服务状态"
|
||
warn "查看日志: docker compose -f docker-compose.prod.yml logs -f"
|
||
echo ""
|
||
}
|
||
|
||
# 显示部署信息
|
||
show_deployment_info() {
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
title " 部署完成!"
|
||
title "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
info "访问地址: ${GREEN}http://localhost:${SERVER_PORT}${NC}"
|
||
echo ""
|
||
|
||
title "【常用命令】"
|
||
echo -e " 查看服务状态: ${CYAN}docker compose -f docker-compose.prod.yml ps${NC}"
|
||
echo -e " 查看日志: ${CYAN}docker compose -f docker-compose.prod.yml logs -f${NC}"
|
||
echo -e " 停止服务: ${CYAN}docker compose -f docker-compose.prod.yml down${NC}"
|
||
echo -e " 重启服务: ${CYAN}docker compose -f docker-compose.prod.yml restart${NC}"
|
||
echo -e " 更新镜像: ${CYAN}docker pull wrbug/polyhermes:latest && docker compose -f docker-compose.prod.yml up -d${NC}"
|
||
echo ""
|
||
|
||
title "【数据库连接信息】"
|
||
echo -e " 主机: ${CYAN}localhost${NC}"
|
||
echo -e " 端口: ${CYAN}${MYSQL_PORT}${NC}"
|
||
echo -e " 数据库: ${CYAN}polyhermes${NC}"
|
||
echo -e " 用户名: ${CYAN}${DB_USERNAME}${NC}"
|
||
echo -e " 密码: ${CYAN}${DB_PASSWORD}${NC}"
|
||
echo ""
|
||
|
||
title "【管理员重置密钥】"
|
||
echo -e " 重置密钥: ${CYAN}${ADMIN_RESET_PASSWORD_KEY}${NC}"
|
||
echo -e " ${YELLOW}💡 此密钥用于重置管理员密码,请妥善保管${NC}"
|
||
echo ""
|
||
|
||
warn "重要提示:"
|
||
warn " 1. 请妥善保管 .env 文件,勿提交到版本控制系统"
|
||
warn " 2. 定期备份数据库数据(位于 Docker volume: polyhermes_mysql-data)"
|
||
warn " 3. 生产环境建议配置反向代理(如 Nginx)并启用 HTTPS"
|
||
echo ""
|
||
}
|
||
|
||
# 主函数
|
||
main() {
|
||
clear
|
||
|
||
echo ""
|
||
title "========================================="
|
||
title " PolyHermes 交互式一键部署脚本 "
|
||
title "========================================="
|
||
echo ""
|
||
|
||
# 执行部署流程
|
||
check_docker
|
||
|
||
# 检查是否已存在 .env 文件
|
||
if [ -f ".env" ]; then
|
||
echo ""
|
||
title "【检测到现有配置】"
|
||
info "发现已存在的 .env 配置文件"
|
||
echo ""
|
||
echo -ne "${YELLOW}是否使用现有配置直接更新镜像?[Y/n]: ${NC}"
|
||
read -r use_existing
|
||
use_existing=${use_existing:-Y}
|
||
|
||
if [[ "$use_existing" =~ ^[Yy]$ ]]; then
|
||
info "将使用现有配置,跳过配置步骤"
|
||
echo ""
|
||
# 从现有 .env 文件读取必要的变量
|
||
source .env 2>/dev/null || true
|
||
else
|
||
warn "将重新配置,现有配置将被备份"
|
||
echo ""
|
||
collect_configuration
|
||
fi
|
||
else
|
||
collect_configuration
|
||
fi
|
||
|
||
download_docker_compose_file
|
||
|
||
# 只有在重新配置时才生成新的 .env 文件
|
||
if [[ ! "$use_existing" =~ ^[Yy]$ ]] || [ ! -f ".env" ]; then
|
||
generate_env_file
|
||
fi
|
||
|
||
# 确认部署
|
||
echo ""
|
||
title "【确认部署】"
|
||
echo -ne "${YELLOW}是否开始部署?[Y/n](回车默认为是): ${NC}"
|
||
read -r confirm
|
||
|
||
# 默认为 Y,只有明确输入 n/N 才取消
|
||
confirm=${confirm:-Y}
|
||
if [[ "$confirm" =~ ^[Nn]$ ]]; then
|
||
warn "部署已取消"
|
||
exit 0
|
||
fi
|
||
|
||
echo ""
|
||
pull_images
|
||
deploy_services
|
||
health_check
|
||
show_deployment_info
|
||
|
||
info "部署流程已完成!"
|
||
}
|
||
|
||
# 捕获 Ctrl+C
|
||
trap 'echo ""; warn "部署已中断"; exit 1' INT
|
||
|
||
# 运行主函数
|
||
main "$@"
|