anxin-ruoyi/docker/scripts/env-config.sh
2026-01-05 01:46:20 +08:00

701 lines
19 KiB
Bash
Raw Permalink 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
# 环境配置管理脚本 (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 "$@"