#!/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 </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 "$@"