QAUP_Management/deploy/docker/backup-restore.sh

782 lines
24 KiB
Bash
Executable File

#!/bin/bash
# QAUP 综合备份和恢复脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
BACKUP_ROOT="$PROJECT_ROOT/backup"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 获取时间戳
get_timestamp() {
date '+%Y%m%d_%H%M%S'
}
# 创建备份目录
create_backup_dirs() {
local timestamp=$1
local backup_dir="$BACKUP_ROOT/$timestamp"
mkdir -p "$backup_dir"/{database,config,uploads,logs,system}
echo "$backup_dir"
}
# 数据库备份
backup_database() {
local backup_dir=$1
local timestamp=$2
print_message $BLUE "备份数据库..."
# 检查数据库容器状态
if ! docker ps --format "{{.Names}}" | grep -q "qaup-postgres"; then
print_message $RED "数据库容器未运行"
return 1
fi
# 创建数据库备份
local db_backup_file="$backup_dir/database/qaup_database_$timestamp.sql"
if docker exec qaup-postgres pg_dump -U postgres -d qaup --verbose --clean --if-exists --create > "$db_backup_file"; then
# 压缩备份文件
gzip "$db_backup_file"
# 创建数据库元信息
cat > "$backup_dir/database/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"database_version": "$(docker exec qaup-postgres psql -U postgres -t -c 'SELECT version();' | xargs)",
"database_size": "$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT pg_size_pretty(pg_database_size('qaup'));" | xargs)",
"table_count": "$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" | xargs)",
"backup_file": "qaup_database_$timestamp.sql.gz",
"backup_size": "$(du -h "$db_backup_file.gz" | cut -f1)"
}
EOF
print_message $GREEN "✓ 数据库备份完成: $(du -h "$db_backup_file.gz" | cut -f1)"
return 0
else
print_message $RED "✗ 数据库备份失败"
return 1
fi
}
# 配置文件备份
backup_config() {
local backup_dir=$1
local timestamp=$2
print_message $BLUE "备份配置文件..."
# 备份环境配置
if [ -f "$PROJECT_ROOT/.env" ]; then
cp "$PROJECT_ROOT/.env" "$backup_dir/config/env_$timestamp"
print_message $GREEN "✓ 环境配置已备份"
fi
# 备份 Docker Compose 配置
cp "$PROJECT_ROOT"/docker-compose*.yml "$backup_dir/config/" 2>/dev/null || true
# 备份应用配置
if [ -d "$PROJECT_ROOT/config" ]; then
cp -r "$PROJECT_ROOT/config" "$backup_dir/config/app_config"
print_message $GREEN "✓ 应用配置已备份"
fi
# 备份 Docker 配置
cp -r "$PROJECT_ROOT/docker" "$backup_dir/config/docker_config"
# 备份 SSL 证书
if [ -d "$PROJECT_ROOT/ssl" ]; then
cp -r "$PROJECT_ROOT/ssl" "$backup_dir/config/ssl_certs"
print_message $GREEN "✓ SSL 证书已备份"
fi
# 创建配置元信息
cat > "$backup_dir/config/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"project_version": "$(grep version $PROJECT_ROOT/pom.xml | head -1 | sed 's/.*<version>\(.*\)<\/version>.*/\1/' | xargs)",
"docker_compose_files": $(ls "$PROJECT_ROOT"/docker-compose*.yml | wc -l),
"config_files_count": $(find "$backup_dir/config" -type f | wc -l)
}
EOF
print_message $GREEN "✓ 配置文件备份完成"
}
# 上传文件备份
backup_uploads() {
local backup_dir=$1
local timestamp=$2
print_message $BLUE "备份上传文件..."
local uploads_dir="$PROJECT_ROOT/data/uploads"
if [ -d "$uploads_dir" ] && [ "$(ls -A "$uploads_dir" 2>/dev/null)" ]; then
# 创建上传文件备份
tar -czf "$backup_dir/uploads/uploads_$timestamp.tar.gz" -C "$PROJECT_ROOT/data" uploads/
# 创建上传文件元信息
cat > "$backup_dir/uploads/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"files_count": $(find "$uploads_dir" -type f | wc -l),
"total_size": "$(du -sh "$uploads_dir" | cut -f1)",
"backup_file": "uploads_$timestamp.tar.gz",
"backup_size": "$(du -h "$backup_dir/uploads/uploads_$timestamp.tar.gz" | cut -f1)"
}
EOF
print_message $GREEN "✓ 上传文件备份完成: $(du -h "$backup_dir/uploads/uploads_$timestamp.tar.gz" | cut -f1)"
else
print_message $YELLOW "⚠ 上传目录为空,跳过备份"
echo '{"backup_time": "'$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')'", "status": "empty"}' > "$backup_dir/uploads/metadata.json"
fi
}
# 日志备份
backup_logs() {
local backup_dir=$1
local timestamp=$2
local days=${3:-7} # 默认备份最近7天的日志
print_message $BLUE "备份日志文件(最近 $days 天)..."
local logs_dir="$PROJECT_ROOT/logs"
if [ -d "$logs_dir" ]; then
# 备份最近的日志文件
find "$logs_dir" -name "*.log" -mtime -$days -exec cp {} "$backup_dir/logs/" \; 2>/dev/null || true
# 备份容器日志
local containers=("qaup-app" "qaup-nginx" "qaup-postgres" "qaup-redis")
for container in "${containers[@]}"; do
if docker ps --format "{{.Names}}" | grep -q "$container"; then
docker logs --since "${days}d" "$container" > "$backup_dir/logs/${container}_$timestamp.log" 2>&1 || true
fi
done
# 压缩日志备份
if [ "$(ls -A "$backup_dir/logs" 2>/dev/null)" ]; then
tar -czf "$backup_dir/logs/logs_$timestamp.tar.gz" -C "$backup_dir/logs" . --exclude="*.tar.gz"
find "$backup_dir/logs" -name "*.log" -delete
# 创建日志元信息
cat > "$backup_dir/logs/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"days_included": $days,
"backup_file": "logs_$timestamp.tar.gz",
"backup_size": "$(du -h "$backup_dir/logs/logs_$timestamp.tar.gz" | cut -f1)"
}
EOF
print_message $GREEN "✓ 日志备份完成: $(du -h "$backup_dir/logs/logs_$timestamp.tar.gz" | cut -f1)"
else
print_message $YELLOW "⚠ 未找到符合条件的日志文件"
fi
else
print_message $YELLOW "⚠ 日志目录不存在"
fi
}
# 系统信息备份
backup_system_info() {
local backup_dir=$1
local timestamp=$2
print_message $BLUE "备份系统信息..."
# 收集系统信息
{
echo "=== 系统信息备份 ==="
echo "备份时间: $(date)"
echo "主机名: $(hostname)"
echo "操作系统: $(uname -a)"
echo ""
echo "=== Docker 信息 ==="
docker version
echo ""
docker-compose version
echo ""
echo "=== 容器状态 ==="
docker ps -a
echo ""
echo "=== 镜像信息 ==="
docker images
echo ""
echo "=== 网络信息 ==="
docker network ls
echo ""
echo "=== 卷信息 ==="
docker volume ls
echo ""
echo "=== 系统资源 ==="
free -h
echo ""
df -h
echo ""
echo "=== 网络端口 ==="
netstat -tuln | grep -E ":(80|443|8080|5432|6379) "
echo ""
} > "$backup_dir/system/system_info_$timestamp.txt"
# 备份 Docker Compose 状态
docker-compose ps > "$backup_dir/system/compose_status_$timestamp.txt" 2>/dev/null || true
# 创建系统元信息
cat > "$backup_dir/system/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"hostname": "$(hostname)",
"os_version": "$(uname -r)",
"docker_version": "$(docker --version | cut -d' ' -f3 | sed 's/,//')",
"compose_version": "$(docker-compose --version | cut -d' ' -f3 | sed 's/,//')",
"running_containers": $(docker ps -q | wc -l),
"total_containers": $(docker ps -aq | wc -l)
}
EOF
print_message $GREEN "✓ 系统信息备份完成"
}
# 完整备份
full_backup() {
local timestamp=$(get_timestamp)
local backup_dir=$(create_backup_dirs "$timestamp")
print_message $GREEN "========================================="
print_message $GREEN "开始完整备份: $timestamp"
print_message $GREEN "========================================="
local backup_success=true
# 执行各项备份
backup_database "$backup_dir" "$timestamp" || backup_success=false
backup_config "$backup_dir" "$timestamp" || backup_success=false
backup_uploads "$backup_dir" "$timestamp" || backup_success=false
backup_logs "$backup_dir" "$timestamp" || backup_success=false
backup_system_info "$backup_dir" "$timestamp" || backup_success=false
# 创建备份清单
create_backup_manifest "$backup_dir" "$timestamp"
# 压缩整个备份
print_message $BLUE "压缩备份文件..."
local backup_archive="$BACKUP_ROOT/qaup_full_backup_$timestamp.tar.gz"
tar -czf "$backup_archive" -C "$BACKUP_ROOT" "$(basename "$backup_dir")"
# 计算校验和
sha256sum "$backup_archive" > "$backup_archive.sha256"
# 清理临时目录
rm -rf "$backup_dir"
if [ "$backup_success" = true ]; then
print_message $GREEN "========================================="
print_message $GREEN "完整备份完成!"
print_message $GREEN "========================================="
echo ""
print_message $BLUE "备份信息:"
echo " 备份文件: $backup_archive"
echo " 文件大小: $(du -h "$backup_archive" | cut -f1)"
echo " 校验文件: $backup_archive.sha256"
echo ""
# 记录备份历史
echo "$(date '+%Y-%m-%d %H:%M:%S') FULL_BACKUP SUCCESS $backup_archive $(du -b "$backup_archive" | cut -f1)" >> "$BACKUP_ROOT/backup_history.log"
return 0
else
print_message $RED "备份过程中出现错误,请检查日志"
return 1
fi
}
# 创建备份清单
create_backup_manifest() {
local backup_dir=$1
local timestamp=$2
cat > "$backup_dir/MANIFEST.json" << EOF
{
"backup_id": "$timestamp",
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"backup_type": "full",
"version": "1.0",
"components": {
"database": {
"included": $([ -f "$backup_dir/database/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "database/metadata.json"
},
"config": {
"included": $([ -f "$backup_dir/config/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "config/metadata.json"
},
"uploads": {
"included": $([ -f "$backup_dir/uploads/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "uploads/metadata.json"
},
"logs": {
"included": $([ -f "$backup_dir/logs/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "logs/metadata.json"
},
"system": {
"included": $([ -f "$backup_dir/system/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "system/metadata.json"
}
},
"total_size": "$(du -sh "$backup_dir" | cut -f1)",
"file_count": $(find "$backup_dir" -type f | wc -l)
}
EOF
}
# 恢复数据库
restore_database() {
local backup_file=$1
print_message $BLUE "恢复数据库..."
if [ ! -f "$backup_file" ]; then
print_message $RED "备份文件不存在: $backup_file"
return 1
fi
# 检查数据库容器状态
if ! docker ps --format "{{.Names}}" | grep -q "qaup-postgres"; then
print_message $RED "数据库容器未运行"
return 1
fi
# 确认恢复操作
print_message $YELLOW "警告: 此操作将覆盖现有数据库数据"
read -p "确定要继续吗? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_message $BLUE "恢复操作已取消"
return 1
fi
# 停止应用服务以避免数据冲突
print_message $BLUE "停止应用服务..."
docker stop qaup-app || true
# 恢复数据库
if [[ "$backup_file" == *.gz ]]; then
# 解压并恢复
if gunzip -c "$backup_file" | docker exec -i qaup-postgres psql -U postgres; then
print_message $GREEN "✓ 数据库恢复成功"
else
print_message $RED "✗ 数据库恢复失败"
return 1
fi
else
# 直接恢复
if docker exec -i qaup-postgres psql -U postgres < "$backup_file"; then
print_message $GREEN "✓ 数据库恢复成功"
else
print_message $RED "✗ 数据库恢复失败"
return 1
fi
fi
# 重启应用服务
print_message $BLUE "重启应用服务..."
docker start qaup-app
# 等待服务就绪
sleep 10
if curl -f -s http://localhost:8080/actuator/health &>/dev/null; then
print_message $GREEN "✓ 应用服务已恢复正常"
else
print_message $YELLOW "⚠ 应用服务可能需要更多时间启动"
fi
return 0
}
# 恢复配置文件
restore_config() {
local backup_dir=$1
print_message $BLUE "恢复配置文件..."
if [ ! -d "$backup_dir" ]; then
print_message $RED "备份目录不存在: $backup_dir"
return 1
fi
# 备份当前配置
local current_backup="$PROJECT_ROOT/config_backup_$(get_timestamp)"
mkdir -p "$current_backup"
[ -f "$PROJECT_ROOT/.env" ] && cp "$PROJECT_ROOT/.env" "$current_backup/"
[ -d "$PROJECT_ROOT/config" ] && cp -r "$PROJECT_ROOT/config" "$current_backup/"
[ -d "$PROJECT_ROOT/docker" ] && cp -r "$PROJECT_ROOT/docker" "$current_backup/"
print_message $BLUE "当前配置已备份到: $current_backup"
# 恢复配置文件
if [ -f "$backup_dir/env_"* ]; then
cp "$backup_dir"/env_* "$PROJECT_ROOT/.env"
print_message $GREEN "✓ 环境配置已恢复"
fi
if [ -d "$backup_dir/app_config" ]; then
rm -rf "$PROJECT_ROOT/config"
cp -r "$backup_dir/app_config" "$PROJECT_ROOT/config"
print_message $GREEN "✓ 应用配置已恢复"
fi
if [ -d "$backup_dir/docker_config" ]; then
rm -rf "$PROJECT_ROOT/docker"
cp -r "$backup_dir/docker_config" "$PROJECT_ROOT/docker"
# 恢复脚本执行权限
find "$PROJECT_ROOT/docker" -name "*.sh" -exec chmod +x {} \;
print_message $GREEN "✓ Docker 配置已恢复"
fi
if [ -d "$backup_dir/ssl_certs" ]; then
rm -rf "$PROJECT_ROOT/ssl"
cp -r "$backup_dir/ssl_certs" "$PROJECT_ROOT/ssl"
chmod 600 "$PROJECT_ROOT/ssl"/*.key 2>/dev/null || true
print_message $GREEN "✓ SSL 证书已恢复"
fi
print_message $GREEN "✓ 配置文件恢复完成"
return 0
}
# 恢复上传文件
restore_uploads() {
local backup_file=$1
print_message $BLUE "恢复上传文件..."
if [ ! -f "$backup_file" ]; then
print_message $RED "备份文件不存在: $backup_file"
return 1
fi
# 备份当前上传文件
local uploads_dir="$PROJECT_ROOT/data/uploads"
if [ -d "$uploads_dir" ] && [ "$(ls -A "$uploads_dir" 2>/dev/null)" ]; then
local current_backup="$PROJECT_ROOT/uploads_backup_$(get_timestamp).tar.gz"
tar -czf "$current_backup" -C "$PROJECT_ROOT/data" uploads/
print_message $BLUE "当前上传文件已备份到: $current_backup"
fi
# 恢复上传文件
mkdir -p "$PROJECT_ROOT/data"
if tar -xzf "$backup_file" -C "$PROJECT_ROOT/data"; then
print_message $GREEN "✓ 上传文件恢复成功"
return 0
else
print_message $RED "✗ 上传文件恢复失败"
return 1
fi
}
# 完整恢复
full_restore() {
local backup_archive=$1
if [ ! -f "$backup_archive" ]; then
print_message $RED "备份文件不存在: $backup_archive"
return 1
fi
print_message $GREEN "========================================="
print_message $GREEN "开始完整恢复"
print_message $GREEN "========================================="
# 验证备份文件
if [ -f "$backup_archive.sha256" ]; then
print_message $BLUE "验证备份文件完整性..."
if sha256sum -c "$backup_archive.sha256"; then
print_message $GREEN "✓ 备份文件完整性验证通过"
else
print_message $RED "✗ 备份文件完整性验证失败"
return 1
fi
fi
# 解压备份文件
local temp_dir="/tmp/qaup_restore_$(get_timestamp)"
mkdir -p "$temp_dir"
print_message $BLUE "解压备份文件..."
if tar -xzf "$backup_archive" -C "$temp_dir"; then
print_message $GREEN "✓ 备份文件解压成功"
else
print_message $RED "✗ 备份文件解压失败"
rm -rf "$temp_dir"
return 1
fi
# 查找备份目录
local backup_dir=$(find "$temp_dir" -maxdepth 1 -type d -name "*" | grep -v "^$temp_dir$" | head -1)
if [ ! -d "$backup_dir" ]; then
print_message $RED "未找到有效的备份目录"
rm -rf "$temp_dir"
return 1
fi
# 读取备份清单
if [ -f "$backup_dir/MANIFEST.json" ]; then
print_message $BLUE "备份清单信息:"
cat "$backup_dir/MANIFEST.json" | grep -E '"backup_time"|"backup_type"|"total_size"' | sed 's/^ / /'
echo ""
fi
# 确认恢复操作
print_message $YELLOW "警告: 此操作将覆盖现有系统数据"
read -p "确定要继续完整恢复吗? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_message $BLUE "恢复操作已取消"
rm -rf "$temp_dir"
return 1
fi
local restore_success=true
# 恢复配置文件
if [ -d "$backup_dir/config" ]; then
restore_config "$backup_dir/config" || restore_success=false
fi
# 恢复数据库
if [ -f "$backup_dir/database"/*.sql.gz ]; then
restore_database "$backup_dir/database"/*.sql.gz || restore_success=false
elif [ -f "$backup_dir/database"/*.sql ]; then
restore_database "$backup_dir/database"/*.sql || restore_success=false
fi
# 恢复上传文件
if [ -f "$backup_dir/uploads"/*.tar.gz ]; then
restore_uploads "$backup_dir/uploads"/*.tar.gz || restore_success=false
fi
# 清理临时文件
rm -rf "$temp_dir"
if [ "$restore_success" = true ]; then
print_message $GREEN "========================================="
print_message $GREEN "完整恢复完成!"
print_message $GREEN "========================================="
echo ""
print_message $BLUE "建议执行以下操作:"
echo " 1. 重启所有服务: ./deploy.sh restart"
echo " 2. 验证系统状态: ./docker/monitor.sh status"
echo " 3. 检查应用功能: 访问前端页面测试"
echo ""
# 记录恢复历史
echo "$(date '+%Y-%m-%d %H:%M:%S') FULL_RESTORE SUCCESS $backup_archive" >> "$BACKUP_ROOT/restore_history.log"
return 0
else
print_message $RED "恢复过程中出现错误,请检查系统状态"
return 1
fi
}
# 列出备份文件
list_backups() {
print_message $BLUE "可用的备份文件:"
echo ""
if [ -d "$BACKUP_ROOT" ]; then
local backup_files=($(find "$BACKUP_ROOT" -name "qaup_full_backup_*.tar.gz" -type f | sort -r))
if [ ${#backup_files[@]} -eq 0 ]; then
print_message $YELLOW "未找到备份文件"
return
fi
local index=1
for backup_file in "${backup_files[@]}"; do
local filename=$(basename "$backup_file")
local size=$(du -h "$backup_file" | cut -f1)
local date=$(echo "$filename" | grep -o '[0-9]\{8\}_[0-9]\{6\}' | sed 's/_/ /')
echo "$index. $filename"
echo " 大小: $size"
echo " 时间: $date"
# 显示校验和状态
if [ -f "$backup_file.sha256" ]; then
echo " 校验: 可用"
else
echo " 校验: 不可用"
fi
echo ""
index=$((index + 1))
done
else
print_message $YELLOW "备份目录不存在: $BACKUP_ROOT"
fi
}
# 清理旧备份
cleanup_old_backups() {
local keep_days=${1:-30}
print_message $BLUE "清理 $keep_days 天前的备份文件..."
if [ ! -d "$BACKUP_ROOT" ]; then
print_message $YELLOW "备份目录不存在"
return
fi
local deleted_count=0
# 清理备份文件
while IFS= read -r -d '' file; do
rm -f "$file" "$file.sha256"
deleted_count=$((deleted_count + 1))
print_message $GREEN "已删除: $(basename "$file")"
done < <(find "$BACKUP_ROOT" -name "qaup_full_backup_*.tar.gz" -mtime +$keep_days -print0)
if [ $deleted_count -eq 0 ]; then
print_message $GREEN "没有需要清理的备份文件"
else
print_message $GREEN "已清理 $deleted_count 个旧备份文件"
fi
}
# 显示帮助信息
show_help() {
echo "QAUP 备份和恢复脚本"
echo ""
echo "用法: $0 [命令] [参数]"
echo ""
echo "备份命令:"
echo " full 完整备份"
echo " database 仅备份数据库"
echo " config 仅备份配置文件"
echo " uploads 仅备份上传文件"
echo ""
echo "恢复命令:"
echo " restore <backup_file> 完整恢复"
echo " restore-db <db_file> 仅恢复数据库"
echo " restore-config <dir> 仅恢复配置"
echo " restore-uploads <file> 仅恢复上传文件"
echo ""
echo "管理命令:"
echo " list 列出备份文件"
echo " cleanup [days] 清理旧备份 (默认30天)"
echo ""
echo "示例:"
echo " $0 full # 完整备份"
echo " $0 restore backup/qaup_full_backup_*.tar.gz # 完整恢复"
echo " $0 list # 列出备份"
echo " $0 cleanup 7 # 清理7天前的备份"
}
# 主函数
main() {
# 确保备份目录存在
mkdir -p "$BACKUP_ROOT"
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
full)
full_backup
;;
database)
local timestamp=$(get_timestamp)
local backup_dir=$(create_backup_dirs "$timestamp")
backup_database "$backup_dir" "$timestamp"
;;
config)
local timestamp=$(get_timestamp)
local backup_dir=$(create_backup_dirs "$timestamp")
backup_config "$backup_dir" "$timestamp"
;;
uploads)
local timestamp=$(get_timestamp)
local backup_dir=$(create_backup_dirs "$timestamp")
backup_uploads "$backup_dir" "$timestamp"
;;
restore)
if [ $# -eq 0 ]; then
print_message $RED "请指定备份文件"
exit 1
fi
full_restore "$1"
;;
restore-db)
if [ $# -eq 0 ]; then
print_message $RED "请指定数据库备份文件"
exit 1
fi
restore_database "$1"
;;
restore-config)
if [ $# -eq 0 ]; then
print_message $RED "请指定配置备份目录"
exit 1
fi
restore_config "$1"
;;
restore-uploads)
if [ $# -eq 0 ]; then
print_message $RED "请指定上传文件备份"
exit 1
fi
restore_uploads "$1"
;;
list)
list_backups
;;
cleanup)
cleanup_old_backups "$@"
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"