#!/bin/bash # 环境配置管理脚本 (env-config.sh) # 实现环境变量配置管理、支持不同环境的参数切换、添加配置验证功能 # Requirements: 6.1, 6.2, 6.3, 6.4 set -e # 脚本目录和项目路径 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DOCKER_DIR="$(dirname "$SCRIPT_DIR")" PROJECT_ROOT="$(dirname "$DOCKER_DIR")" ENVIRONMENTS_DIR="${DOCKER_DIR}/environments" CONFIGS_DIR="${DOCKER_DIR}/configs" # 颜色定义 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 # 日志函数 log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_debug() { echo -e "${BLUE}[DEBUG]${NC} $1" } log_success() { echo -e "${CYAN}[SUCCESS]${NC} $1" } # 显示帮助信息 show_help() { cat << EOF 环境配置管理脚本 - 若依框架Docker部署 用法: $0 [命令] [环境] [选项] 命令: validate 验证环境配置文件 switch 切换到指定环境配置 show 显示环境配置信息 compare 比较不同环境配置 backup 备份当前环境配置 restore 恢复环境配置 template 生成环境配置模板 check 检查配置完整性 list 列出所有可用环境 help 显示此帮助信息 环境: development (dev) 开发环境 staging (stage) 测试环境 production (prod) 生产环境 选项: --force 强制执行操作(跳过确认) --verbose 详细输出模式 --dry-run 预览模式(不实际执行) 示例: $0 validate development # 验证开发环境配置 $0 switch production # 切换到生产环境 $0 show staging # 显示测试环境配置 $0 compare dev prod # 比较开发和生产环境配置 $0 backup # 备份当前配置 $0 template development # 生成开发环境配置模板 EOF } # 验证环境参数 validate_environment() { local env=$1 case $env in development|dev) echo "development" ;; staging|stage) echo "staging" ;; production|prod) echo "production" ;; *) log_error "无效的环境: $env" log_info "支持的环境: development, staging, production" exit 1 ;; esac } # 获取环境配置文件路径 get_env_file() { local env=$1 echo "${ENVIRONMENTS_DIR}/.env.${env}" } # 获取当前活动的环境配置文件路径 get_active_env_file() { echo "${DOCKER_DIR}/.env" } # 检查环境配置文件是否存在 check_env_file_exists() { local env=$1 local env_file=$(get_env_file $env) if [ ! -f "$env_file" ]; then log_error "环境配置文件不存在: $env_file" return 1 fi return 0 } # 验证环境配置文件 (Requirements 6.1, 6.2, 6.3, 6.4) validate_env_config() { local env=$1 local env_file=$(get_env_file $env) local errors=0 log_info "验证 $env 环境配置文件: $env_file" if ! check_env_file_exists $env; then return 1 fi # 必需的配置项列表 local required_vars=( "ENVIRONMENT" "COMPOSE_PROJECT_NAME" "DB_HOST" "DB_PORT" "DB_NAME" "DB_USER" "DB_PASSWORD" "MYSQL_ROOT_PASSWORD" "BACKEND_PORT" "FRONTEND_PORT" "SPRING_PROFILES_ACTIVE" "API_BASE_URL" "LOG_LEVEL" "NETWORK_NAME" "MYSQL_DATA_PATH" ) # 检查必需的环境变量 log_debug "检查必需的环境变量..." for var in "${required_vars[@]}"; do if ! grep -q "^${var}=" "$env_file"; then log_error "缺少必需的环境变量: $var" ((errors++)) else local value=$(grep "^${var}=" "$env_file" | cut -d'=' -f2-) if [ -z "$value" ] || [ "$value" = "CHANGE_ME_PRODUCTION_PASSWORD" ] || [ "$value" = "CHANGE_ME_ROOT_PASSWORD" ]; then log_warn "环境变量 $var 需要设置有效值" ((errors++)) fi fi done # 验证数据库连接配置 (Requirement 6.1) log_debug "验证数据库连接配置..." local db_port=$(grep "^DB_PORT=" "$env_file" | cut -d'=' -f2) if [ -n "$db_port" ] && ! [[ "$db_port" =~ ^[0-9]+$ ]]; then log_error "DB_PORT 必须是数字: $db_port" ((errors++)) fi # 验证前端API基础URL配置 (Requirement 6.2) log_debug "验证前端API基础URL配置..." local api_url=$(grep "^API_BASE_URL=" "$env_file" | cut -d'=' -f2) if [ -n "$api_url" ] && ! [[ "$api_url" =~ ^https?:// ]]; then log_error "API_BASE_URL 必须是有效的HTTP/HTTPS URL: $api_url" ((errors++)) fi # 验证容器资源限制配置 (Requirement 6.3) log_debug "验证容器资源限制配置..." local memory_vars=("FRONTEND_MEMORY_LIMIT" "BACKEND_MEMORY_LIMIT" "DATABASE_MEMORY_LIMIT") for var in "${memory_vars[@]}"; do local value=$(grep "^${var}=" "$env_file" | cut -d'=' -f2) if [ -n "$value" ] && ! [[ "$value" =~ ^[0-9]+$ ]]; then log_error "$var 必须是数字 (MB): $value" ((errors++)) fi done local cpu_vars=("FRONTEND_CPU_LIMIT" "BACKEND_CPU_LIMIT" "DATABASE_CPU_LIMIT") for var in "${cpu_vars[@]}"; do local value=$(grep "^${var}=" "$env_file" | cut -d'=' -f2) if [ -n "$value" ] && ! [[ "$value" =~ ^[0-9]+\.?[0-9]*$ ]]; then log_error "$var 必须是有效的CPU限制值: $value" ((errors++)) fi done # 验证日志级别和输出路径配置 (Requirement 6.4) log_debug "验证日志配置..." local log_level=$(grep "^LOG_LEVEL=" "$env_file" | cut -d'=' -f2) if [ -n "$log_level" ] && ! [[ "$log_level" =~ ^(DEBUG|INFO|WARN|ERROR)$ ]]; then log_error "LOG_LEVEL 必须是 DEBUG, INFO, WARN 或 ERROR: $log_level" ((errors++)) fi local log_path=$(grep "^LOG_PATH=" "$env_file" | cut -d'=' -f2) if [ -n "$log_path" ] && [[ "$log_path" =~ ^/ ]] && [ ! -d "$(dirname "$log_path")" ]; then log_warn "日志路径的父目录不存在: $(dirname "$log_path")" fi # 验证网络配置 log_debug "验证网络配置..." local subnet=$(grep "^SUBNET=" "$env_file" | cut -d'=' -f2) if [ -n "$subnet" ] && ! [[ "$subnet" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]]; then log_error "SUBNET 必须是有效的CIDR格式: $subnet" ((errors++)) fi # 验证端口配置 log_debug "验证端口配置..." local ports=("BACKEND_PORT" "FRONTEND_PORT" "DB_PORT") for port_var in "${ports[@]}"; do local port=$(grep "^${port_var}=" "$env_file" | cut -d'=' -f2) if [ -n "$port" ]; then if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then log_error "$port_var 必须是1-65535之间的数字: $port" ((errors++)) fi fi done # 报告验证结果 if [ $errors -eq 0 ]; then log_success "$env 环境配置验证通过" return 0 else log_error "$env 环境配置验证失败,发现 $errors 个错误" return 1 fi } # 切换环境配置 switch_environment() { local env=$1 local force=${2:-false} local dry_run=${3:-false} log_info "切换到 $env 环境配置..." # 验证目标环境配置 if ! validate_env_config $env; then log_error "目标环境配置验证失败,无法切换" return 1 fi local source_file=$(get_env_file $env) local target_file=$(get_active_env_file) # 备份当前配置 if [ -f "$target_file" ] && [ "$force" != "true" ]; then local backup_file="${target_file}.backup.$(date +%Y%m%d_%H%M%S)" if [ "$dry_run" != "true" ]; then cp "$target_file" "$backup_file" log_info "当前配置已备份到: $backup_file" else log_info "[DRY-RUN] 将备份当前配置到: $backup_file" fi fi # 复制环境配置 if [ "$dry_run" != "true" ]; then cp "$source_file" "$target_file" log_success "已切换到 $env 环境配置" # 显示关键配置信息 show_key_config "$env" else log_info "[DRY-RUN] 将复制 $source_file 到 $target_file" fi } # 显示环境配置信息 show_env_config() { local env=$1 local env_file=$(get_env_file $env) if ! check_env_file_exists $env; then return 1 fi log_info "显示 $env 环境配置:" echo "----------------------------------------" # 显示关键配置项 local key_vars=( "ENVIRONMENT" "COMPOSE_PROJECT_NAME" "DB_NAME" "DB_USER" "BACKEND_PORT" "FRONTEND_PORT" "API_BASE_URL" "LOG_LEVEL" "SPRING_PROFILES_ACTIVE" ) for var in "${key_vars[@]}"; do local value=$(grep "^${var}=" "$env_file" | cut -d'=' -f2-) if [ -n "$value" ]; then printf "%-25s: %s\n" "$var" "$value" fi done echo "----------------------------------------" } # 显示关键配置信息 show_key_config() { local env=$1 local env_file=$(get_env_file $env) echo log_info "当前环境关键配置:" echo " 环境: $(grep "^ENVIRONMENT=" "$env_file" | cut -d'=' -f2)" echo " 项目: $(grep "^COMPOSE_PROJECT_NAME=" "$env_file" | cut -d'=' -f2)" echo " 前端端口: $(grep "^FRONTEND_PORT=" "$env_file" | cut -d'=' -f2)" echo " 后端端口: $(grep "^BACKEND_PORT=" "$env_file" | cut -d'=' -f2)" echo " 数据库: $(grep "^DB_NAME=" "$env_file" | cut -d'=' -f2)" echo " 日志级别: $(grep "^LOG_LEVEL=" "$env_file" | cut -d'=' -f2)" echo } # 比较不同环境配置 compare_environments() { local env1=$1 local env2=$2 log_info "比较 $env1 和 $env2 环境配置..." if ! check_env_file_exists $env1 || ! check_env_file_exists $env2; then return 1 fi local file1=$(get_env_file $env1) local file2=$(get_env_file $env2) echo "==========================================" echo "配置差异 ($env1 vs $env2):" echo "==========================================" # 使用diff命令比较文件 if command -v diff &> /dev/null; then diff -u "$file1" "$file2" || true else log_warn "diff命令不可用,使用基本比较" # 基本比较逻辑 local vars1=$(grep "^[A-Z]" "$file1" | cut -d'=' -f1 | sort) local vars2=$(grep "^[A-Z]" "$file2" | cut -d'=' -f1 | sort) # 找出差异变量 local all_vars=$(echo -e "$vars1\n$vars2" | sort -u) for var in $all_vars; do local val1=$(grep "^${var}=" "$file1" | cut -d'=' -f2- 2>/dev/null || echo "") local val2=$(grep "^${var}=" "$file2" | cut -d'=' -f2- 2>/dev/null || echo "") if [ "$val1" != "$val2" ]; then printf "%-25s: %-20s | %s\n" "$var" "$val1" "$val2" fi done fi echo "==========================================" } # 备份环境配置 backup_config() { local timestamp=$(date +%Y%m%d_%H%M%S) local backup_dir="${DOCKER_DIR}/backups/config_${timestamp}" log_info "备份环境配置到: $backup_dir" mkdir -p "$backup_dir" # 备份所有环境配置文件 cp -r "$ENVIRONMENTS_DIR" "$backup_dir/" # 备份当前活动配置 if [ -f "$(get_active_env_file)" ]; then cp "$(get_active_env_file)" "$backup_dir/active.env" fi # 创建备份信息文件 cat > "$backup_dir/backup_info.txt" << EOF 备份时间: $(date) 备份内容: 环境配置文件 备份路径: $backup_dir 当前环境: $(grep "^ENVIRONMENT=" "$(get_active_env_file)" 2>/dev/null | cut -d'=' -f2 || echo "未知") EOF log_success "配置备份完成: $backup_dir" } # 恢复环境配置 restore_config() { local backup_path=$1 if [ -z "$backup_path" ]; then log_error "请指定备份路径" return 1 fi if [ ! -d "$backup_path" ]; then log_error "备份路径不存在: $backup_path" return 1 fi log_info "从备份恢复环境配置: $backup_path" # 确认操作 read -p "确认恢复配置? 这将覆盖当前配置 (y/N): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then # 恢复环境配置目录 if [ -d "$backup_path/environments" ]; then cp -r "$backup_path/environments/"* "$ENVIRONMENTS_DIR/" log_info "环境配置文件已恢复" fi # 恢复活动配置 if [ -f "$backup_path/active.env" ]; then cp "$backup_path/active.env" "$(get_active_env_file)" log_info "活动配置文件已恢复" fi log_success "配置恢复完成" else log_info "取消恢复操作" fi } # 生成环境配置模板 generate_template() { local env=$1 local template_file="${ENVIRONMENTS_DIR}/.env.${env}.template" log_info "生成 $env 环境配置模板: $template_file" cat > "$template_file" << EOF # $env 环境配置模板 # 复制此文件为 .env.$env 并根据实际环境修改配置 ENVIRONMENT=$env COMPOSE_PROJECT_NAME=anxin-$env # 数据库配置 (Requirement 6.1) DB_HOST=anxin-mysql DB_PORT=3306 DB_NAME=anxin_$env DB_USER=anxin_$env DB_PASSWORD=CHANGE_ME_PASSWORD MYSQL_ROOT_PASSWORD=CHANGE_ME_ROOT_PASSWORD # 后端服务配置 BACKEND_PORT=8080 SPRING_PROFILES_ACTIVE=$env JAVA_OPTS=-Xms512m -Xmx1024m # 前端服务配置 (Requirement 6.2) FRONTEND_PORT=80 API_BASE_URL=http://localhost:8080 # 容器资源配置 (Requirement 6.3) FRONTEND_MEMORY_LIMIT=256 BACKEND_MEMORY_LIMIT=1024 DATABASE_MEMORY_LIMIT=512 FRONTEND_CPU_LIMIT=0.5 BACKEND_CPU_LIMIT=1.0 DATABASE_CPU_LIMIT=0.5 # 日志配置 (Requirement 6.4) LOG_LEVEL=INFO LOG_PATH=./logs/$env LOG_MAX_SIZE=100MB LOG_MAX_FILES=10 # 网络配置 NETWORK_NAME=anxin-$env-network SUBNET=172.20.0.0/16 # 卷配置 MYSQL_DATA_PATH=./data/$env/mysql LOG_DATA_PATH=./data/$env/logs CONFIG_DATA_PATH=./data/$env/configs EOF log_success "模板生成完成: $template_file" } # 检查配置完整性 check_config_integrity() { log_info "检查所有环境配置完整性..." local environments=("development" "staging" "production") local total_errors=0 for env in "${environments[@]}"; do echo if validate_env_config $env; then log_success "$env 环境配置完整" else ((total_errors++)) fi done echo if [ $total_errors -eq 0 ]; then log_success "所有环境配置检查通过" return 0 else log_error "发现 $total_errors 个环境配置问题" return 1 fi } # 列出所有可用环境 list_environments() { log_info "可用环境配置:" for env_file in "$ENVIRONMENTS_DIR"/.env.*; do if [ -f "$env_file" ] && [[ ! "$env_file" =~ \.template$ ]] && [[ ! "$env_file" =~ \.example$ ]]; then local env_name=$(basename "$env_file" | sed 's/^\.env\.//') local env_desc="" case $env_name in development) env_desc="开发环境" ;; staging) env_desc="测试环境" ;; production) env_desc="生产环境" ;; *) env_desc="自定义环境" ;; esac printf " %-12s - %s\n" "$env_name" "$env_desc" # 显示关键信息 if [ -f "$env_file" ]; then local project=$(grep "^COMPOSE_PROJECT_NAME=" "$env_file" | cut -d'=' -f2) local frontend_port=$(grep "^FRONTEND_PORT=" "$env_file" | cut -d'=' -f2) local backend_port=$(grep "^BACKEND_PORT=" "$env_file" | cut -d'=' -f2) printf " 项目: %s, 前端: %s, 后端: %s\n" "$project" "$frontend_port" "$backend_port" fi fi done echo # 显示当前活动环境 local active_file=$(get_active_env_file) if [ -f "$active_file" ]; then local current_env=$(grep "^ENVIRONMENT=" "$active_file" | cut -d'=' -f2) log_info "当前活动环境: $current_env" else log_warn "未找到活动环境配置" fi } # 解析命令行参数 parse_args() { FORCE=false VERBOSE=false DRY_RUN=false while [[ $# -gt 0 ]]; do case $1 in --force) FORCE=true shift ;; --verbose) VERBOSE=true shift ;; --dry-run) DRY_RUN=true shift ;; *) break ;; esac done COMMAND=$1 ENVIRONMENT=$2 ENVIRONMENT2=$3 } # 主函数 main() { parse_args "$@" case $COMMAND in validate) if [ -z "$ENVIRONMENT" ]; then log_error "请指定环境" show_help exit 1 fi env=$(validate_environment "$ENVIRONMENT") validate_env_config "$env" ;; switch) if [ -z "$ENVIRONMENT" ]; then log_error "请指定环境" show_help exit 1 fi env=$(validate_environment "$ENVIRONMENT") switch_environment "$env" "$FORCE" "$DRY_RUN" ;; show) if [ -z "$ENVIRONMENT" ]; then log_error "请指定环境" show_help exit 1 fi env=$(validate_environment "$ENVIRONMENT") show_env_config "$env" ;; compare) if [ -z "$ENVIRONMENT" ] || [ -z "$ENVIRONMENT2" ]; then log_error "请指定两个环境进行比较" show_help exit 1 fi env1=$(validate_environment "$ENVIRONMENT") env2=$(validate_environment "$ENVIRONMENT2") compare_environments "$env1" "$env2" ;; backup) backup_config ;; restore) restore_config "$ENVIRONMENT" ;; template) if [ -z "$ENVIRONMENT" ]; then log_error "请指定环境" show_help exit 1 fi env=$(validate_environment "$ENVIRONMENT") generate_template "$env" ;; check) check_config_integrity ;; list) list_environments ;; help|--help|-h) show_help ;; *) log_error "未知命令: $COMMAND" show_help exit 1 ;; esac } # 检查必要的目录 if [ ! -d "$ENVIRONMENTS_DIR" ]; then log_error "环境配置目录不存在: $ENVIRONMENTS_DIR" exit 1 fi # 执行主函数 main "$@"